diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2012-05-23 13:05:43 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2012-05-23 13:05:43 -0700 |
commit | 2e341ca686042aa464efa755447e7bcee91d1eb6 (patch) | |
tree | c6b16b6b6a6e871fa04396cb2c7eb759bcad5be3 /sound/soc | |
parent | 927ad551031798d4cba49766549600bbb33872d7 (diff) | |
parent | 85e184e4c3cd3e2285ceab91ff8f0cac094e8a85 (diff) |
Merge tag 'sound-3.5' of git://git.kernel.org/pub/scm/linux/kernel/git/tiwai/sound
Pull sound updates from Takashi Iwai:
"This is the first big chunk for 3.5 merges of sound stuff.
There are a few big changes in different areas. First off, the
streaming logic of USB-audio endpoints has been largely rewritten for
the better support of "implicit feedback". If anything about USB got
broken, this change has to be checked.
For HD-audio, the resume procedure was changed; instead of delaying
the resume of the hardware until the first use, now waking up
immediately at resume. This is for buggy BIOS.
For ASoC, dynamic PCM support and the improved support for digital
links between off-SoC devices are major framework changes.
Some highlights are below:
* HD-audio
- Avoid accesses of invalid pin-control bits that may stall the codec
- V-ref setup cleanups
- Fix the races in power-saving code
- Fix the races in codec cache hashes and connection lists
- Split some common codes for BIOS auto-parser to hda_auto_parser.c
- Changed the PM resume code to wake up immediately for buggy BIOS
- Creative SoundCore3D support
- Add Conexant CX20751/2/3/4 codec support
* ASoC
- Dynamic PCM support, allowing support for SoCs with internal
routing through components with tight sequencing and formatting
constraints within their internal paths or where there are multiple
components connected with CPU managed DMA controllers inside the
SoC.
- Greatly improved support for direct digital links between off-SoC
devices, providing a much simpler way of connecting things like
digital basebands to CODECs.
- Much more fine grained and robust locking, cleaning up some of the
confusion that crept in with multi-component.
- CPU support for nVidia Tegra 30 I2S and audio hub controllers and
ST-Ericsson MSP I2S controolers
- New CODEC drivers for Cirrus CS42L52, LAPIS Semiconductor ML26124,
Texas Instruments LM49453.
- Some regmap changes needed by the Tegra I2S driver.
- mc13783 audio support.
* Misc
- Rewrite with module_pci_driver()
- Xonar DGX support for snd-oxygen
- Improvement of packet handling in snd-firewire driver
- New USB-endpoint streaming logic
- Enhanced M-audio FTU quirks and relevant cleanups
- Increment the support of OSS devices to 256
- snd-aloop accuracy improvement
There are a few more pending changes for 3.5, but they will be sent
slightly later as partly depending on the changes of DRM."
Fix up conflicts in regmap (due to duplicate patches, with some further
updates then having already come in from the regmap tree). Also some
fairly trivial context conflicts in the imx and mcx soc drivers.
* tag 'sound-3.5' of git://git.kernel.org/pub/scm/linux/kernel/git/tiwai/sound: (280 commits)
ALSA: snd-usb: fix stream info output in /proc
ALSA: pcm - Add proper state checks to snd_pcm_drain()
ALSA: sh: Fix up namespace collision in sh_dac_audio.
ALSA: hda/realtek - Fix unused variable compile warning
ASoC: sh: fsi: enable chip specific data transfer mode
ASoC: sh: fsi: call fsi_hw_startup/shutdown from fsi_dai_trigger()
ASoC: sh: fsi: use same format for IN/OUT
ASoC: sh: fsi: add fsi_version() and removed meaningless version check
ASoC: sh: fsi: use register field macro name on IN/OUT_DMAC
ASoC: tegra: Add machine driver for WM8753 codec
ALSA: hda - Fix possible races of accesses to connection list array
ASoC: OMAP: HDMI: Introduce codec
ARM: mx31_3ds: Add sound support
ASoC: imx-mc13783 cleanup
mx31moboard: Add sound support
ASoC: mc13783 codec cleanups
ASoC: add imx-mc13783 sound support
ASoC: Add mc13783 codec
mfd: mc13xxx: add codec platform data
ASoC: don't flip master of DT-instantiated DAI links
...
Diffstat (limited to 'sound/soc')
158 files changed, 17406 insertions, 4930 deletions
diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig index 91c985599d3..40b2ad1bb1c 100644 --- a/sound/soc/Kconfig +++ b/sound/soc/Kconfig @@ -35,7 +35,6 @@ source "sound/soc/blackfin/Kconfig" source "sound/soc/davinci/Kconfig" source "sound/soc/ep93xx/Kconfig" source "sound/soc/fsl/Kconfig" -source "sound/soc/imx/Kconfig" source "sound/soc/jz4740/Kconfig" source "sound/soc/nuc900/Kconfig" source "sound/soc/omap/Kconfig" @@ -48,9 +47,13 @@ source "sound/soc/s6000/Kconfig" source "sound/soc/sh/Kconfig" source "sound/soc/tegra/Kconfig" source "sound/soc/txx9/Kconfig" +source "sound/soc/ux500/Kconfig" # Supported codecs source "sound/soc/codecs/Kconfig" +# generic frame-work +source "sound/soc/generic/Kconfig" + endif # SND_SOC diff --git a/sound/soc/Makefile b/sound/soc/Makefile index 2feaf376e94..70990f4017f 100644 --- a/sound/soc/Makefile +++ b/sound/soc/Makefile @@ -6,13 +6,13 @@ obj-$(CONFIG_SND_SOC_DMAENGINE_PCM) += snd-soc-dmaengine-pcm.o obj-$(CONFIG_SND_SOC) += snd-soc-core.o obj-$(CONFIG_SND_SOC) += codecs/ +obj-$(CONFIG_SND_SOC) += generic/ obj-$(CONFIG_SND_SOC) += atmel/ obj-$(CONFIG_SND_SOC) += au1x/ obj-$(CONFIG_SND_SOC) += blackfin/ obj-$(CONFIG_SND_SOC) += davinci/ obj-$(CONFIG_SND_SOC) += ep93xx/ obj-$(CONFIG_SND_SOC) += fsl/ -obj-$(CONFIG_SND_SOC) += imx/ obj-$(CONFIG_SND_SOC) += jz4740/ obj-$(CONFIG_SND_SOC) += mid-x86/ obj-$(CONFIG_SND_SOC) += mxs/ @@ -25,3 +25,4 @@ obj-$(CONFIG_SND_SOC) += s6000/ obj-$(CONFIG_SND_SOC) += sh/ obj-$(CONFIG_SND_SOC) += tegra/ obj-$(CONFIG_SND_SOC) += txx9/ +obj-$(CONFIG_SND_SOC) += ux500/ diff --git a/sound/soc/blackfin/bf5xx-ssm2602.c b/sound/soc/blackfin/bf5xx-ssm2602.c index b39ad356b92..7dbeef1099b 100644 --- a/sound/soc/blackfin/bf5xx-ssm2602.c +++ b/sound/soc/blackfin/bf5xx-ssm2602.c @@ -44,16 +44,8 @@ static struct snd_soc_card bf5xx_ssm2602; -static int bf5xx_ssm2602_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *params) +static int bf5xx_ssm2602_dai_init(struct snd_soc_pcm_runtime *rtd) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_dai *codec_dai = rtd->codec_dai; - unsigned int clk = 0; - int ret = 0; - - pr_debug("%s rate %d format %x\n", __func__, params_rate(params), - params_format(params)); /* * If you are using a crystal source which frequency is not 12MHz * then modify the below case statement with frequency of the crystal. @@ -61,31 +53,10 @@ static int bf5xx_ssm2602_hw_params(struct snd_pcm_substream *substream, * If you are using the SPORT to generate clocking then this is * where to do it. */ - - switch (params_rate(params)) { - case 8000: - case 16000: - case 48000: - case 96000: - case 11025: - case 22050: - case 44100: - clk = 12000000; - break; - } - - ret = snd_soc_dai_set_sysclk(codec_dai, SSM2602_SYSCLK, clk, + return snd_soc_dai_set_sysclk(rtd->codec_dai, SSM2602_SYSCLK, 12000000, SND_SOC_CLOCK_IN); - if (ret < 0) - return ret; - - return 0; } -static struct snd_soc_ops bf5xx_ssm2602_ops = { - .hw_params = bf5xx_ssm2602_hw_params, -}; - /* CODEC is master for BCLK and LRC in this configuration. */ #define BF5XX_SSM2602_DAIFMT (SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | \ SND_SOC_DAIFMT_CBM_CFM) @@ -98,7 +69,7 @@ static struct snd_soc_dai_link bf5xx_ssm2602_dai[] = { .codec_dai_name = "ssm2602-hifi", .platform_name = "bfin-i2s-pcm-audio", .codec_name = "ssm2602.0-001b", - .ops = &bf5xx_ssm2602_ops, + .init = bf5xx_ssm2602_dai_init, .dai_fmt = BF5XX_SSM2602_DAIFMT, }, { @@ -108,7 +79,7 @@ static struct snd_soc_dai_link bf5xx_ssm2602_dai[] = { .codec_dai_name = "ssm2602-hifi", .platform_name = "bfin-i2s-pcm-audio", .codec_name = "ssm2602.0-001b", - .ops = &bf5xx_ssm2602_ops, + .init = bf5xx_ssm2602_dai_init, .dai_fmt = BF5XX_SSM2602_DAIFMT, }, }; diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 59d8efaa17e..1e1613a438d 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -29,6 +29,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_ALC5632 if I2C select SND_SOC_CQ0093VC if MFD_DAVINCI_VOICECODEC select SND_SOC_CS42L51 if I2C + select SND_SOC_CS42L52 if I2C select SND_SOC_CS42L73 if I2C select SND_SOC_CS4270 if I2C select SND_SOC_CS4271 if SND_SOC_I2C_AND_SPI @@ -37,11 +38,15 @@ config SND_SOC_ALL_CODECS select SND_SOC_DFBMCS320 select SND_SOC_JZ4740_CODEC select SND_SOC_LM4857 if I2C + select SND_SOC_LM49453 if I2C select SND_SOC_MAX98088 if I2C select SND_SOC_MAX98095 if I2C select SND_SOC_MAX9850 if I2C select SND_SOC_MAX9768 if I2C select SND_SOC_MAX9877 if I2C + select SND_SOC_MC13783 if MFD_MC13XXX + select SND_SOC_ML26124 if I2C + select SND_SOC_OMAP_HDMI_CODEC if OMAP4_DSS_HDMI select SND_SOC_PCM3008 select SND_SOC_RT5631 if I2C select SND_SOC_SGTL5000 if I2C @@ -181,6 +186,9 @@ config SND_SOC_CQ0093VC config SND_SOC_CS42L51 tristate +config SND_SOC_CS42L52 + tristate + config SND_SOC_CS42L73 tristate @@ -217,6 +225,9 @@ config SND_SOC_DFBMCS320 config SND_SOC_DMIC tristate +config SND_SOC_LM49453 + tristate + config SND_SOC_MAX98088 tristate @@ -226,6 +237,9 @@ config SND_SOC_MAX98095 config SND_SOC_MAX9850 tristate +config SND_SOC_OMAP_HDMI_CODEC + tristate + config SND_SOC_PCM3008 tristate @@ -435,5 +449,11 @@ config SND_SOC_MAX9768 config SND_SOC_MAX9877 tristate +config SND_SOC_MC13783 + tristate + +config SND_SOC_ML26124 + tristate + config SND_SOC_TPA6130A2 tristate diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 6662eb0cdcc..fc27fec3948 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -15,6 +15,7 @@ snd-soc-ak4642-objs := ak4642.o snd-soc-ak4671-objs := ak4671.o snd-soc-cq93vc-objs := cq93vc.o snd-soc-cs42l51-objs := cs42l51.o +snd-soc-cs42l52-objs := cs42l52.o snd-soc-cs42l73-objs := cs42l73.o snd-soc-cs4270-objs := cs4270.o snd-soc-cs4271-objs := cs4271.o @@ -25,10 +26,14 @@ snd-soc-dmic-objs := dmic.o snd-soc-jz4740-codec-objs := jz4740.o snd-soc-l3-objs := l3.o snd-soc-lm4857-objs := lm4857.o +snd-soc-lm49453-objs := lm49453.o snd-soc-max9768-objs := max9768.o snd-soc-max98088-objs := max98088.o snd-soc-max98095-objs := max98095.o snd-soc-max9850-objs := max9850.o +snd-soc-mc13783-objs := mc13783.o +snd-soc-ml26124-objs := ml26124.o +snd-soc-omap-hdmi-codec-objs := omap-hdmi.o snd-soc-pcm3008-objs := pcm3008.o snd-soc-rt5631-objs := rt5631.o snd-soc-sgtl5000-objs := sgtl5000.o @@ -121,6 +126,7 @@ obj-$(CONFIG_SND_SOC_ALC5623) += snd-soc-alc5623.o obj-$(CONFIG_SND_SOC_ALC5632) += snd-soc-alc5632.o obj-$(CONFIG_SND_SOC_CQ0093VC) += snd-soc-cq93vc.o obj-$(CONFIG_SND_SOC_CS42L51) += snd-soc-cs42l51.o +obj-$(CONFIG_SND_SOC_CS42L52) += snd-soc-cs42l52.o obj-$(CONFIG_SND_SOC_CS42L73) += snd-soc-cs42l73.o obj-$(CONFIG_SND_SOC_CS4270) += snd-soc-cs4270.o obj-$(CONFIG_SND_SOC_CS4271) += snd-soc-cs4271.o @@ -128,13 +134,17 @@ obj-$(CONFIG_SND_SOC_CX20442) += snd-soc-cx20442.o obj-$(CONFIG_SND_SOC_DA7210) += snd-soc-da7210.o obj-$(CONFIG_SND_SOC_DFBMCS320) += snd-soc-dfbmcs320.o obj-$(CONFIG_SND_SOC_DMIC) += snd-soc-dmic.o +obj-$(CONFIG_SND_SOC_JZ4740_CODEC) += snd-soc-jz4740-codec.o obj-$(CONFIG_SND_SOC_L3) += snd-soc-l3.o obj-$(CONFIG_SND_SOC_LM4857) += snd-soc-lm4857.o -obj-$(CONFIG_SND_SOC_JZ4740_CODEC) += snd-soc-jz4740-codec.o +obj-$(CONFIG_SND_SOC_LM49453) += snd-soc-lm49453.o obj-$(CONFIG_SND_SOC_MAX9768) += snd-soc-max9768.o obj-$(CONFIG_SND_SOC_MAX98088) += snd-soc-max98088.o obj-$(CONFIG_SND_SOC_MAX98095) += snd-soc-max98095.o obj-$(CONFIG_SND_SOC_MAX9850) += snd-soc-max9850.o +obj-$(CONFIG_SND_SOC_MC13783) += snd-soc-mc13783.o +obj-$(CONFIG_SND_SOC_ML26124) += snd-soc-ml26124.o +obj-$(CONFIG_SND_SOC_OMAP_HDMI_CODEC) += snd-soc-omap-hdmi-codec.o obj-$(CONFIG_SND_SOC_PCM3008) += snd-soc-pcm3008.o obj-$(CONFIG_SND_SOC_RT5631) += snd-soc-rt5631.o obj-$(CONFIG_SND_SOC_SGTL5000) += snd-soc-sgtl5000.o diff --git a/sound/soc/codecs/ac97.c b/sound/soc/codecs/ac97.c index 1bbad4c16d2..2023c749f23 100644 --- a/sound/soc/codecs/ac97.c +++ b/sound/soc/codecs/ac97.c @@ -26,13 +26,11 @@ static int ac97_prepare(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { - struct snd_pcm_runtime *runtime = substream->runtime; - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_codec *codec = dai->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, runtime->rate); + return snd_ac97_set_rate(codec->ac97, reg, substream->runtime->rate); } #define STD_AC97_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\ diff --git a/sound/soc/codecs/ad1836.c b/sound/soc/codecs/ad1836.c index 12e3b411855..c67b50d8b31 100644 --- a/sound/soc/codecs/ad1836.c +++ b/sound/soc/codecs/ad1836.c @@ -162,9 +162,7 @@ static int ad1836_hw_params(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { int word_len = 0; - - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_codec *codec = dai->codec; /* bit size */ switch (params_format(params)) { diff --git a/sound/soc/codecs/ad193x.c b/sound/soc/codecs/ad193x.c index a4a6bef2c0b..13e62be4f99 100644 --- a/sound/soc/codecs/ad193x.c +++ b/sound/soc/codecs/ad193x.c @@ -245,9 +245,7 @@ static int ad193x_hw_params(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { int word_len = 0, master_rate = 0; - - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_codec *codec = dai->codec; struct ad193x_priv *ad193x = snd_soc_codec_get_drvdata(codec); /* bit size */ diff --git a/sound/soc/codecs/adau1701.c b/sound/soc/codecs/adau1701.c index 78e9ce48bb9..3d50fc8646b 100644 --- a/sound/soc/codecs/adau1701.c +++ b/sound/soc/codecs/adau1701.c @@ -258,8 +258,7 @@ static int adau1701_set_playback_pcm_format(struct snd_soc_codec *codec, static int adau1701_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_codec *codec = dai->codec; snd_pcm_format_t format; unsigned int val; diff --git a/sound/soc/codecs/ak4104.c b/sound/soc/codecs/ak4104.c index ceb96ecf558..31d4483245d 100644 --- a/sound/soc/codecs/ak4104.c +++ b/sound/soc/codecs/ak4104.c @@ -88,8 +88,7 @@ static int ak4104_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_codec *codec = dai->codec; int val = 0; /* set the IEC958 bits: consumer mode, no copyright bit */ diff --git a/sound/soc/codecs/ak4535.c b/sound/soc/codecs/ak4535.c index 838ae8b22b5..618fdc30f73 100644 --- a/sound/soc/codecs/ak4535.c +++ b/sound/soc/codecs/ak4535.c @@ -262,8 +262,7 @@ static int ak4535_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_codec *codec = dai->codec; struct ak4535_priv *ak4535 = snd_soc_codec_get_drvdata(codec); u8 mode2 = snd_soc_read(codec, AK4535_MODE2) & ~(0x3 << 5); int rate = params_rate(params), fs = 256; diff --git a/sound/soc/codecs/ak4641.c b/sound/soc/codecs/ak4641.c index c4d165a4bdd..543a12f471b 100644 --- a/sound/soc/codecs/ak4641.c +++ b/sound/soc/codecs/ak4641.c @@ -296,8 +296,7 @@ static int ak4641_i2s_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_codec *codec = dai->codec; struct ak4641_priv *ak4641 = snd_soc_codec_get_drvdata(codec); int rate = params_rate(params), fs = 256; u8 mode2; @@ -517,67 +516,24 @@ static int ak4641_resume(struct snd_soc_codec *codec) static int ak4641_probe(struct snd_soc_codec *codec) { - struct ak4641_platform_data *pdata = codec->dev->platform_data; int ret; - - if (pdata) { - if (gpio_is_valid(pdata->gpio_power)) { - ret = gpio_request_one(pdata->gpio_power, - GPIOF_OUT_INIT_LOW, "ak4641 power"); - if (ret) - goto err_out; - } - if (gpio_is_valid(pdata->gpio_npdn)) { - ret = gpio_request_one(pdata->gpio_npdn, - GPIOF_OUT_INIT_LOW, "ak4641 npdn"); - if (ret) - goto err_gpio; - - udelay(1); /* > 150 ns */ - gpio_set_value(pdata->gpio_npdn, 1); - } - } - ret = snd_soc_codec_set_cache_io(codec, 8, 8, SND_SOC_I2C); if (ret != 0) { dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret); - goto err_register; + return ret; } /* power on device */ ak4641_set_bias_level(codec, SND_SOC_BIAS_STANDBY); return 0; - -err_register: - if (pdata) { - if (gpio_is_valid(pdata->gpio_power)) - gpio_set_value(pdata->gpio_power, 0); - if (gpio_is_valid(pdata->gpio_npdn)) - gpio_free(pdata->gpio_npdn); - } -err_gpio: - if (pdata && gpio_is_valid(pdata->gpio_power)) - gpio_free(pdata->gpio_power); -err_out: - return ret; } static int ak4641_remove(struct snd_soc_codec *codec) { - struct ak4641_platform_data *pdata = codec->dev->platform_data; - ak4641_set_bias_level(codec, SND_SOC_BIAS_OFF); - if (pdata) { - if (gpio_is_valid(pdata->gpio_power)) { - gpio_set_value(pdata->gpio_power, 0); - gpio_free(pdata->gpio_power); - } - if (gpio_is_valid(pdata->gpio_npdn)) - gpio_free(pdata->gpio_npdn); - } return 0; } @@ -604,6 +560,7 @@ static struct snd_soc_codec_driver soc_codec_dev_ak4641 = { static int __devinit ak4641_i2c_probe(struct i2c_client *i2c, const struct i2c_device_id *id) { + struct ak4641_platform_data *pdata = i2c->dev.platform_data; struct ak4641_priv *ak4641; int ret; @@ -612,16 +569,62 @@ static int __devinit ak4641_i2c_probe(struct i2c_client *i2c, if (!ak4641) return -ENOMEM; + if (pdata) { + if (gpio_is_valid(pdata->gpio_power)) { + ret = gpio_request_one(pdata->gpio_power, + GPIOF_OUT_INIT_LOW, "ak4641 power"); + if (ret) + goto err_out; + } + if (gpio_is_valid(pdata->gpio_npdn)) { + ret = gpio_request_one(pdata->gpio_npdn, + GPIOF_OUT_INIT_LOW, "ak4641 npdn"); + if (ret) + goto err_gpio; + + udelay(1); /* > 150 ns */ + gpio_set_value(pdata->gpio_npdn, 1); + } + } + i2c_set_clientdata(i2c, ak4641); ret = snd_soc_register_codec(&i2c->dev, &soc_codec_dev_ak4641, ak4641_dai, ARRAY_SIZE(ak4641_dai)); + if (ret != 0) + goto err_gpio2; + + return 0; + +err_gpio2: + if (pdata) { + if (gpio_is_valid(pdata->gpio_power)) + gpio_set_value(pdata->gpio_power, 0); + if (gpio_is_valid(pdata->gpio_npdn)) + gpio_free(pdata->gpio_npdn); + } +err_gpio: + if (pdata && gpio_is_valid(pdata->gpio_power)) + gpio_free(pdata->gpio_power); +err_out: return ret; } static int __devexit ak4641_i2c_remove(struct i2c_client *i2c) { + struct ak4641_platform_data *pdata = i2c->dev.platform_data; + snd_soc_unregister_codec(&i2c->dev); + + if (pdata) { + if (gpio_is_valid(pdata->gpio_power)) { + gpio_set_value(pdata->gpio_power, 0); + gpio_free(pdata->gpio_power); + } + if (gpio_is_valid(pdata->gpio_npdn)) + gpio_free(pdata->gpio_npdn); + } + return 0; } @@ -641,23 +644,7 @@ static struct i2c_driver ak4641_i2c_driver = { .id_table = ak4641_i2c_id, }; -static int __init ak4641_modinit(void) -{ - int ret; - - ret = i2c_add_driver(&ak4641_i2c_driver); - if (ret != 0) - pr_err("Failed to register AK4641 I2C driver: %d\n", ret); - - return ret; -} -module_init(ak4641_modinit); - -static void __exit ak4641_exit(void) -{ - i2c_del_driver(&ak4641_i2c_driver); -} -module_exit(ak4641_exit); +module_i2c_driver(ak4641_i2c_driver); MODULE_DESCRIPTION("SoC AK4641 driver"); MODULE_AUTHOR("Harald Welte <laforge@gnufiish.org>"); diff --git a/sound/soc/codecs/alc5623.c b/sound/soc/codecs/alc5623.c index d47b62ddb21..1960478ce6b 100644 --- a/sound/soc/codecs/alc5623.c +++ b/sound/soc/codecs/alc5623.c @@ -705,8 +705,7 @@ static int alc5623_set_dai_fmt(struct snd_soc_dai *codec_dai, static int alc5623_pcm_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_codec *codec = dai->codec; struct alc5623_priv *alc5623 = snd_soc_codec_get_drvdata(codec); int coeff, rate; u16 iface; @@ -1084,25 +1083,7 @@ static struct i2c_driver alc5623_i2c_driver = { .id_table = alc5623_i2c_table, }; -static int __init alc5623_modinit(void) -{ - int ret; - - ret = i2c_add_driver(&alc5623_i2c_driver); - if (ret != 0) { - printk(KERN_ERR "%s: can't add i2c driver", __func__); - return ret; - } - - return ret; -} -module_init(alc5623_modinit); - -static void __exit alc5623_modexit(void) -{ - i2c_del_driver(&alc5623_i2c_driver); -} -module_exit(alc5623_modexit); +module_i2c_driver(alc5623_i2c_driver); MODULE_DESCRIPTION("ASoC alc5621/2/3 driver"); MODULE_AUTHOR("Arnaud Patard <arnaud.patard@rtp-net.org>"); diff --git a/sound/soc/codecs/alc5632.c b/sound/soc/codecs/alc5632.c index e2111e0ccad..7dd02420b36 100644 --- a/sound/soc/codecs/alc5632.c +++ b/sound/soc/codecs/alc5632.c @@ -861,8 +861,7 @@ static int alc5632_set_dai_fmt(struct snd_soc_dai *codec_dai, static int alc5632_pcm_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_codec *codec = dai->codec; int coeff, rate; u16 iface; @@ -1131,7 +1130,7 @@ static __devinit int alc5632_i2c_probe(struct i2c_client *client, i2c_set_clientdata(client, alc5632); - alc5632->regmap = regmap_init_i2c(client, &alc5632_regmap); + alc5632->regmap = devm_regmap_init_i2c(client, &alc5632_regmap); if (IS_ERR(alc5632->regmap)) { ret = PTR_ERR(alc5632->regmap); dev_err(&client->dev, "regmap_init() failed: %d\n", ret); @@ -1143,7 +1142,6 @@ static __devinit int alc5632_i2c_probe(struct i2c_client *client, if (ret1 != 0 || ret2 != 0) { dev_err(&client->dev, "Failed to read chip ID: ret1=%d, ret2=%d\n", ret1, ret2); - regmap_exit(alc5632->regmap); return -EIO; } @@ -1152,14 +1150,12 @@ static __devinit int alc5632_i2c_probe(struct i2c_client *client, if ((vid1 != 0x10EC) || (vid2 != id->driver_data)) { dev_err(&client->dev, "Device is not a ALC5632: VID1=0x%x, VID2=0x%x\n", vid1, vid2); - regmap_exit(alc5632->regmap); return -EINVAL; } ret = alc5632_reset(alc5632->regmap); if (ret < 0) { dev_err(&client->dev, "Failed to issue reset\n"); - regmap_exit(alc5632->regmap); return ret; } @@ -1177,7 +1173,6 @@ static __devinit int alc5632_i2c_probe(struct i2c_client *client, if (ret < 0) { dev_err(&client->dev, "Failed to register codec: %d\n", ret); - regmap_exit(alc5632->regmap); return ret; } @@ -1186,9 +1181,7 @@ static __devinit int alc5632_i2c_probe(struct i2c_client *client, static __devexit int alc5632_i2c_remove(struct i2c_client *client) { - struct alc5632_priv *alc5632 = i2c_get_clientdata(client); snd_soc_unregister_codec(&client->dev); - regmap_exit(alc5632->regmap); return 0; } @@ -1209,25 +1202,7 @@ static struct i2c_driver alc5632_i2c_driver = { .id_table = alc5632_i2c_table, }; -static int __init alc5632_modinit(void) -{ - int ret; - - ret = i2c_add_driver(&alc5632_i2c_driver); - if (ret != 0) { - printk(KERN_ERR "%s: can't add i2c driver", __func__); - return ret; - } - - return ret; -} -module_init(alc5632_modinit); - -static void __exit alc5632_modexit(void) -{ - i2c_del_driver(&alc5632_i2c_driver); -} -module_exit(alc5632_modexit); +module_i2c_driver(alc5632_i2c_driver); MODULE_DESCRIPTION("ASoC ALC5632 driver"); MODULE_AUTHOR("Leon Romanovsky <leon@leon.nu>"); diff --git a/sound/soc/codecs/cs4270.c b/sound/soc/codecs/cs4270.c index 1d672f52866..047917f0b8a 100644 --- a/sound/soc/codecs/cs4270.c +++ b/sound/soc/codecs/cs4270.c @@ -307,8 +307,7 @@ static int cs4270_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_codec *codec = dai->codec; struct cs4270_private *cs4270 = snd_soc_codec_get_drvdata(codec); int ret; unsigned int i; @@ -600,10 +599,12 @@ static int cs4270_soc_suspend(struct snd_soc_codec *codec) static int cs4270_soc_resume(struct snd_soc_codec *codec) { struct cs4270_private *cs4270 = snd_soc_codec_get_drvdata(codec); - int reg; + int reg, ret; - regulator_bulk_enable(ARRAY_SIZE(cs4270->supplies), - cs4270->supplies); + ret = regulator_bulk_enable(ARRAY_SIZE(cs4270->supplies), + cs4270->supplies); + if (ret != 0) + return ret; /* In case the device was put to hard reset during sleep, we need to * wait 500ns here before any I2C communication. */ diff --git a/sound/soc/codecs/cs4271.c b/sound/soc/codecs/cs4271.c index bf7141280a7..9eb01d7d58a 100644 --- a/sound/soc/codecs/cs4271.c +++ b/sound/soc/codecs/cs4271.c @@ -318,8 +318,7 @@ static int cs4271_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_codec *codec = dai->codec; struct cs4271_private *cs4271 = snd_soc_codec_get_drvdata(codec); int i, ret; unsigned int ratio, val; diff --git a/sound/soc/codecs/cs42l51.c b/sound/soc/codecs/cs42l51.c index a8bf588e874..091d0193f50 100644 --- a/sound/soc/codecs/cs42l51.c +++ b/sound/soc/codecs/cs42l51.c @@ -141,15 +141,15 @@ static const struct soc_enum cs42l51_chan_mix = static const struct snd_kcontrol_new cs42l51_snd_controls[] = { SOC_DOUBLE_R_SX_TLV("PCM Playback Volume", CS42L51_PCMA_VOL, CS42L51_PCMB_VOL, - 7, 0xffffff99, 0x18, adc_pcm_tlv), + 6, 0x19, 0x7F, adc_pcm_tlv), SOC_DOUBLE_R("PCM Playback Switch", CS42L51_PCMA_VOL, CS42L51_PCMB_VOL, 7, 1, 1), SOC_DOUBLE_R_SX_TLV("Analog Playback Volume", CS42L51_AOUTA_VOL, CS42L51_AOUTB_VOL, - 8, 0xffffff19, 0x18, aout_tlv), + 0, 0x34, 0xE4, aout_tlv), SOC_DOUBLE_R_SX_TLV("ADC Mixer Volume", CS42L51_ADCA_VOL, CS42L51_ADCB_VOL, - 7, 0xffffff99, 0x18, adc_pcm_tlv), + 6, 0x19, 0x7F, adc_pcm_tlv), SOC_DOUBLE_R("ADC Mixer Switch", CS42L51_ADCA_VOL, CS42L51_ADCB_VOL, 7, 1, 1), SOC_SINGLE("Playback Deemphasis Switch", CS42L51_DAC_CTL, 3, 1, 0), @@ -356,8 +356,7 @@ static int cs42l51_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_codec *codec = dai->codec; struct cs42l51_private *cs42l51 = snd_soc_codec_get_drvdata(codec); int ret; unsigned int i; diff --git a/sound/soc/codecs/cs42l52.c b/sound/soc/codecs/cs42l52.c new file mode 100644 index 00000000000..a7109413aef --- /dev/null +++ b/sound/soc/codecs/cs42l52.c @@ -0,0 +1,1295 @@ +/* + * cs42l52.c -- CS42L52 ALSA SoC audio driver + * + * Copyright 2012 CirrusLogic, Inc. + * + * Author: Georgi Vlaev <joe@nucleusys.com> + * Author: Brian Austin <brian.austin@cirrus.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/version.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/i2c.h> +#include <linux/input.h> +#include <linux/regmap.h> +#include <linux/slab.h> +#include <linux/workqueue.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/initval.h> +#include <sound/tlv.h> +#include <sound/cs42l52.h> +#include "cs42l52.h" + +struct sp_config { + u8 spc, format, spfs; + u32 srate; +}; + +struct cs42l52_private { + struct regmap *regmap; + struct snd_soc_codec *codec; + struct device *dev; + struct sp_config config; + struct cs42l52_platform_data pdata; + u32 sysclk; + u8 mclksel; + u32 mclk; + u8 flags; +#if defined(CONFIG_INPUT) || defined(CONFIG_INPUT_MODULE) + struct input_dev *beep; + struct work_struct beep_work; + int beep_rate; +#endif +}; + +static const struct reg_default cs42l52_reg_defaults[] = { + { CS42L52_PWRCTL1, 0x9F }, /* r02 PWRCTL 1 */ + { CS42L52_PWRCTL2, 0x07 }, /* r03 PWRCTL 2 */ + { CS42L52_PWRCTL3, 0xFF }, /* r04 PWRCTL 3 */ + { CS42L52_CLK_CTL, 0xA0 }, /* r05 Clocking Ctl */ + { CS42L52_IFACE_CTL1, 0x00 }, /* r06 Interface Ctl 1 */ + { CS42L52_ADC_PGA_A, 0x80 }, /* r08 Input A Select */ + { CS42L52_ADC_PGA_B, 0x80 }, /* r09 Input B Select */ + { CS42L52_ANALOG_HPF_CTL, 0xA5 }, /* r0A Analog HPF Ctl */ + { CS42L52_ADC_HPF_FREQ, 0x00 }, /* r0B ADC HPF Corner Freq */ + { CS42L52_ADC_MISC_CTL, 0x00 }, /* r0C Misc. ADC Ctl */ + { CS42L52_PB_CTL1, 0x60 }, /* r0D Playback Ctl 1 */ + { CS42L52_MISC_CTL, 0x02 }, /* r0E Misc. Ctl */ + { CS42L52_PB_CTL2, 0x00 }, /* r0F Playback Ctl 2 */ + { CS42L52_MICA_CTL, 0x00 }, /* r10 MICA Amp Ctl */ + { CS42L52_MICB_CTL, 0x00 }, /* r11 MICB Amp Ctl */ + { CS42L52_PGAA_CTL, 0x00 }, /* r12 PGAA Vol, Misc. */ + { CS42L52_PGAB_CTL, 0x00 }, /* r13 PGAB Vol, Misc. */ + { CS42L52_PASSTHRUA_VOL, 0x00 }, /* r14 Bypass A Vol */ + { CS42L52_PASSTHRUB_VOL, 0x00 }, /* r15 Bypass B Vol */ + { CS42L52_ADCA_VOL, 0x00 }, /* r16 ADCA Volume */ + { CS42L52_ADCB_VOL, 0x00 }, /* r17 ADCB Volume */ + { CS42L52_ADCA_MIXER_VOL, 0x80 }, /* r18 ADCA Mixer Volume */ + { CS42L52_ADCB_MIXER_VOL, 0x80 }, /* r19 ADCB Mixer Volume */ + { CS42L52_PCMA_MIXER_VOL, 0x00 }, /* r1A PCMA Mixer Volume */ + { CS42L52_PCMB_MIXER_VOL, 0x00 }, /* r1B PCMB Mixer Volume */ + { CS42L52_BEEP_FREQ, 0x00 }, /* r1C Beep Freq on Time */ + { CS42L52_BEEP_VOL, 0x00 }, /* r1D Beep Volume off Time */ + { CS42L52_BEEP_TONE_CTL, 0x00 }, /* r1E Beep Tone Cfg. */ + { CS42L52_TONE_CTL, 0x00 }, /* r1F Tone Ctl */ + { CS42L52_MASTERA_VOL, 0x88 }, /* r20 Master A Volume */ + { CS42L52_MASTERB_VOL, 0x00 }, /* r21 Master B Volume */ + { CS42L52_HPA_VOL, 0x00 }, /* r22 Headphone A Volume */ + { CS42L52_HPB_VOL, 0x00 }, /* r23 Headphone B Volume */ + { CS42L52_SPKA_VOL, 0x00 }, /* r24 Speaker A Volume */ + { CS42L52_SPKB_VOL, 0x00 }, /* r25 Speaker B Volume */ + { CS42L52_ADC_PCM_MIXER, 0x00 }, /* r26 Channel Mixer and Swap */ + { CS42L52_LIMITER_CTL1, 0x00 }, /* r27 Limit Ctl 1 Thresholds */ + { CS42L52_LIMITER_CTL2, 0x7F }, /* r28 Limit Ctl 2 Release Rate */ + { CS42L52_LIMITER_AT_RATE, 0xC0 }, /* r29 Limiter Attack Rate */ + { CS42L52_ALC_CTL, 0x00 }, /* r2A ALC Ctl 1 Attack Rate */ + { CS42L52_ALC_RATE, 0x3F }, /* r2B ALC Release Rate */ + { CS42L52_ALC_THRESHOLD, 0x3f }, /* r2C ALC Thresholds */ + { CS42L52_NOISE_GATE_CTL, 0x00 }, /* r2D Noise Gate Ctl */ + { CS42L52_CLK_STATUS, 0x00 }, /* r2E Overflow and Clock Status */ + { CS42L52_BATT_COMPEN, 0x00 }, /* r2F battery Compensation */ + { CS42L52_BATT_LEVEL, 0x00 }, /* r30 VP Battery Level */ + { CS42L52_SPK_STATUS, 0x00 }, /* r31 Speaker Status */ + { CS42L52_TEM_CTL, 0x3B }, /* r32 Temp Ctl */ + { CS42L52_THE_FOLDBACK, 0x00 }, /* r33 Foldback */ +}; + +static bool cs42l52_readable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case CS42L52_CHIP: + case CS42L52_PWRCTL1: + case CS42L52_PWRCTL2: + case CS42L52_PWRCTL3: + case CS42L52_CLK_CTL: + case CS42L52_IFACE_CTL1: + case CS42L52_IFACE_CTL2: + case CS42L52_ADC_PGA_A: + case CS42L52_ADC_PGA_B: + case CS42L52_ANALOG_HPF_CTL: + case CS42L52_ADC_HPF_FREQ: + case CS42L52_ADC_MISC_CTL: + case CS42L52_PB_CTL1: + case CS42L52_MISC_CTL: + case CS42L52_PB_CTL2: + case CS42L52_MICA_CTL: + case CS42L52_MICB_CTL: + case CS42L52_PGAA_CTL: + case CS42L52_PGAB_CTL: + case CS42L52_PASSTHRUA_VOL: + case CS42L52_PASSTHRUB_VOL: + case CS42L52_ADCA_VOL: + case CS42L52_ADCB_VOL: + case CS42L52_ADCA_MIXER_VOL: + case CS42L52_ADCB_MIXER_VOL: + case CS42L52_PCMA_MIXER_VOL: + case CS42L52_PCMB_MIXER_VOL: + case CS42L52_BEEP_FREQ: + case CS42L52_BEEP_VOL: + case CS42L52_BEEP_TONE_CTL: + case CS42L52_TONE_CTL: + case CS42L52_MASTERA_VOL: + case CS42L52_MASTERB_VOL: + case CS42L52_HPA_VOL: + case CS42L52_HPB_VOL: + case CS42L52_SPKA_VOL: + case CS42L52_SPKB_VOL: + case CS42L52_ADC_PCM_MIXER: + case CS42L52_LIMITER_CTL1: + case CS42L52_LIMITER_CTL2: + case CS42L52_LIMITER_AT_RATE: + case CS42L52_ALC_CTL: + case CS42L52_ALC_RATE: + case CS42L52_ALC_THRESHOLD: + case CS42L52_NOISE_GATE_CTL: + case CS42L52_CLK_STATUS: + case CS42L52_BATT_COMPEN: + case CS42L52_BATT_LEVEL: + case CS42L52_SPK_STATUS: + case CS42L52_TEM_CTL: + case CS42L52_THE_FOLDBACK: + case CS42L52_CHARGE_PUMP: + return true; + default: + return false; + } +} + +static bool cs42l52_volatile_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case CS42L52_IFACE_CTL2: + case CS42L52_CLK_STATUS: + case CS42L52_BATT_LEVEL: + case CS42L52_SPK_STATUS: + case CS42L52_CHARGE_PUMP: + return 1; + default: + return 0; + } +} + +static DECLARE_TLV_DB_SCALE(hl_tlv, -10200, 50, 0); + +static DECLARE_TLV_DB_SCALE(hpd_tlv, -9600, 50, 1); + +static DECLARE_TLV_DB_SCALE(ipd_tlv, -9600, 100, 0); + +static DECLARE_TLV_DB_SCALE(mic_tlv, 1600, 100, 0); + +static DECLARE_TLV_DB_SCALE(pga_tlv, -600, 50, 0); + +static const unsigned int limiter_tlv[] = { + TLV_DB_RANGE_HEAD(2), + 0, 2, TLV_DB_SCALE_ITEM(-3000, 600, 0), + 3, 7, TLV_DB_SCALE_ITEM(-1200, 300, 0), +}; + +static const char * const cs42l52_adca_text[] = { + "Input1A", "Input2A", "Input3A", "Input4A", "PGA Input Left"}; + +static const char * const cs42l52_adcb_text[] = { + "Input1B", "Input2B", "Input3B", "Input4B", "PGA Input Right"}; + +static const struct soc_enum adca_enum = + SOC_ENUM_SINGLE(CS42L52_ADC_PGA_A, 5, + ARRAY_SIZE(cs42l52_adca_text), cs42l52_adca_text); + +static const struct soc_enum adcb_enum = + SOC_ENUM_SINGLE(CS42L52_ADC_PGA_B, 5, + ARRAY_SIZE(cs42l52_adcb_text), cs42l52_adcb_text); + +static const struct snd_kcontrol_new adca_mux = + SOC_DAPM_ENUM("Left ADC Input Capture Mux", adca_enum); + +static const struct snd_kcontrol_new adcb_mux = + SOC_DAPM_ENUM("Right ADC Input Capture Mux", adcb_enum); + +static const char * const mic_bias_level_text[] = { + "0.5 +VA", "0.6 +VA", "0.7 +VA", + "0.8 +VA", "0.83 +VA", "0.91 +VA" +}; + +static const struct soc_enum mic_bias_level_enum = + SOC_ENUM_SINGLE(CS42L52_IFACE_CTL1, 0, + ARRAY_SIZE(mic_bias_level_text), mic_bias_level_text); + +static const char * const cs42l52_mic_text[] = { "Single", "Differential" }; + +static const struct soc_enum mica_enum = + SOC_ENUM_SINGLE(CS42L52_MICA_CTL, 5, + ARRAY_SIZE(cs42l52_mic_text), cs42l52_mic_text); + +static const struct soc_enum micb_enum = + SOC_ENUM_SINGLE(CS42L52_MICB_CTL, 5, + ARRAY_SIZE(cs42l52_mic_text), cs42l52_mic_text); + +static const struct snd_kcontrol_new mica_mux = + SOC_DAPM_ENUM("Left Mic Input Capture Mux", mica_enum); + +static const struct snd_kcontrol_new micb_mux = + SOC_DAPM_ENUM("Right Mic Input Capture Mux", micb_enum); + +static const char * const digital_output_mux_text[] = {"ADC", "DSP"}; + +static const struct soc_enum digital_output_mux_enum = + SOC_ENUM_SINGLE(CS42L52_ADC_MISC_CTL, 6, + ARRAY_SIZE(digital_output_mux_text), + digital_output_mux_text); + +static const struct snd_kcontrol_new digital_output_mux = + SOC_DAPM_ENUM("Digital Output Mux", digital_output_mux_enum); + +static const char * const hp_gain_num_text[] = { + "0.3959", "0.4571", "0.5111", "0.6047", + "0.7099", "0.8399", "1.000", "1.1430" +}; + +static const struct soc_enum hp_gain_enum = + SOC_ENUM_SINGLE(CS42L52_PB_CTL1, 4, + ARRAY_SIZE(hp_gain_num_text), hp_gain_num_text); + +static const char * const beep_pitch_text[] = { + "C4", "C5", "D5", "E5", "F5", "G5", "A5", "B5", + "C6", "D6", "E6", "F6", "G6", "A6", "B6", "C7" +}; + +static const struct soc_enum beep_pitch_enum = + SOC_ENUM_SINGLE(CS42L52_BEEP_FREQ, 4, + ARRAY_SIZE(beep_pitch_text), beep_pitch_text); + +static const char * const beep_ontime_text[] = { + "86 ms", "430 ms", "780 ms", "1.20 s", "1.50 s", + "1.80 s", "2.20 s", "2.50 s", "2.80 s", "3.20 s", + "3.50 s", "3.80 s", "4.20 s", "4.50 s", "4.80 s", "5.20 s" +}; + +static const struct soc_enum beep_ontime_enum = + SOC_ENUM_SINGLE(CS42L52_BEEP_FREQ, 0, + ARRAY_SIZE(beep_ontime_text), beep_ontime_text); + +static const char * const beep_offtime_text[] = { + "1.23 s", "2.58 s", "3.90 s", "5.20 s", + "6.60 s", "8.05 s", "9.35 s", "10.80 s" +}; + +static const struct soc_enum beep_offtime_enum = + SOC_ENUM_SINGLE(CS42L52_BEEP_VOL, 5, + ARRAY_SIZE(beep_offtime_text), beep_offtime_text); + +static const char * const beep_config_text[] = { + "Off", "Single", "Multiple", "Continuous" +}; + +static const struct soc_enum beep_config_enum = + SOC_ENUM_SINGLE(CS42L52_BEEP_TONE_CTL, 6, + ARRAY_SIZE(beep_config_text), beep_config_text); + +static const char * const beep_bass_text[] = { + "50 Hz", "100 Hz", "200 Hz", "250 Hz" +}; + +static const struct soc_enum beep_bass_enum = + SOC_ENUM_SINGLE(CS42L52_BEEP_TONE_CTL, 1, + ARRAY_SIZE(beep_bass_text), beep_bass_text); + +static const char * const beep_treble_text[] = { + "5 kHz", "7 kHz", "10 kHz", " 15 kHz" +}; + +static const struct soc_enum beep_treble_enum = + SOC_ENUM_SINGLE(CS42L52_BEEP_TONE_CTL, 3, + ARRAY_SIZE(beep_treble_text), beep_treble_text); + +static const char * const ng_threshold_text[] = { + "-34dB", "-37dB", "-40dB", "-43dB", + "-46dB", "-52dB", "-58dB", "-64dB" +}; + +static const struct soc_enum ng_threshold_enum = + SOC_ENUM_SINGLE(CS42L52_NOISE_GATE_CTL, 2, + ARRAY_SIZE(ng_threshold_text), ng_threshold_text); + +static const char * const cs42l52_ng_delay_text[] = { + "50ms", "100ms", "150ms", "200ms"}; + +static const struct soc_enum ng_delay_enum = + SOC_ENUM_SINGLE(CS42L52_NOISE_GATE_CTL, 0, + ARRAY_SIZE(cs42l52_ng_delay_text), cs42l52_ng_delay_text); + +static const char * const cs42l52_ng_type_text[] = { + "Apply Specific", "Apply All" +}; + +static const struct soc_enum ng_type_enum = + SOC_ENUM_SINGLE(CS42L52_NOISE_GATE_CTL, 6, + ARRAY_SIZE(cs42l52_ng_type_text), cs42l52_ng_type_text); + +static const char * const left_swap_text[] = { + "Left", "LR 2", "Right"}; + +static const char * const right_swap_text[] = { + "Right", "LR 2", "Left"}; + +static const unsigned int swap_values[] = { 0, 1, 3 }; + +static const struct soc_enum adca_swap_enum = + SOC_VALUE_ENUM_SINGLE(CS42L52_ADC_PCM_MIXER, 2, 1, + ARRAY_SIZE(left_swap_text), + left_swap_text, + swap_values); + +static const struct snd_kcontrol_new adca_mixer = + SOC_DAPM_ENUM("Route", adca_swap_enum); + +static const struct soc_enum pcma_swap_enum = + SOC_VALUE_ENUM_SINGLE(CS42L52_ADC_PCM_MIXER, 6, 1, + ARRAY_SIZE(left_swap_text), + left_swap_text, + swap_values); + +static const struct snd_kcontrol_new pcma_mixer = + SOC_DAPM_ENUM("Route", pcma_swap_enum); + +static const struct soc_enum adcb_swap_enum = + SOC_VALUE_ENUM_SINGLE(CS42L52_ADC_PCM_MIXER, 0, 1, + ARRAY_SIZE(right_swap_text), + right_swap_text, + swap_values); + +static const struct snd_kcontrol_new adcb_mixer = + SOC_DAPM_ENUM("Route", adcb_swap_enum); + +static const struct soc_enum pcmb_swap_enum = + SOC_VALUE_ENUM_SINGLE(CS42L52_ADC_PCM_MIXER, 4, 1, + ARRAY_SIZE(right_swap_text), + right_swap_text, + swap_values); + +static const struct snd_kcontrol_new pcmb_mixer = + SOC_DAPM_ENUM("Route", pcmb_swap_enum); + + +static const struct snd_kcontrol_new passthrul_ctl = + SOC_DAPM_SINGLE("Switch", CS42L52_MISC_CTL, 6, 1, 0); + +static const struct snd_kcontrol_new passthrur_ctl = + SOC_DAPM_SINGLE("Switch", CS42L52_MISC_CTL, 7, 1, 0); + +static const struct snd_kcontrol_new spkl_ctl = + SOC_DAPM_SINGLE("Switch", CS42L52_PWRCTL3, 0, 1, 1); + +static const struct snd_kcontrol_new spkr_ctl = + SOC_DAPM_SINGLE("Switch", CS42L52_PWRCTL3, 2, 1, 1); + +static const struct snd_kcontrol_new hpl_ctl = + SOC_DAPM_SINGLE("Switch", CS42L52_PWRCTL3, 4, 1, 1); + +static const struct snd_kcontrol_new hpr_ctl = + SOC_DAPM_SINGLE("Switch", CS42L52_PWRCTL3, 6, 1, 1); + +static const struct snd_kcontrol_new cs42l52_snd_controls[] = { + + SOC_DOUBLE_R_SX_TLV("Master Volume", CS42L52_MASTERA_VOL, + CS42L52_MASTERB_VOL, 0, 0x34, 0xE4, hl_tlv), + + SOC_DOUBLE_R_SX_TLV("Headphone Volume", CS42L52_HPA_VOL, + CS42L52_HPB_VOL, 0, 0x34, 0xCC, hpd_tlv), + + SOC_ENUM("Headphone Analog Gain", hp_gain_enum), + + SOC_DOUBLE_R_SX_TLV("Speaker Volume", CS42L52_SPKA_VOL, + CS42L52_SPKB_VOL, 7, 0x1, 0xff, hl_tlv), + + SOC_DOUBLE_R_SX_TLV("Bypass Volume", CS42L52_PASSTHRUA_VOL, + CS42L52_PASSTHRUB_VOL, 6, 0x18, 0x90, pga_tlv), + + SOC_DOUBLE("Bypass Mute", CS42L52_MISC_CTL, 4, 5, 1, 0), + + SOC_DOUBLE_R_TLV("MIC Gain Volume", CS42L52_MICA_CTL, + CS42L52_MICB_CTL, 0, 0x10, 0, mic_tlv), + + SOC_ENUM("MIC Bias Level", mic_bias_level_enum), + + SOC_DOUBLE_R_SX_TLV("ADC Volume", CS42L52_ADCA_VOL, + CS42L52_ADCB_VOL, 7, 0x80, 0xA0, ipd_tlv), + SOC_DOUBLE_R_SX_TLV("ADC Mixer Volume", + CS42L52_ADCA_MIXER_VOL, CS42L52_ADCB_MIXER_VOL, + 6, 0x7f, 0x19, ipd_tlv), + + SOC_DOUBLE("ADC Switch", CS42L52_ADC_MISC_CTL, 0, 1, 1, 0), + + SOC_DOUBLE_R("ADC Mixer Switch", CS42L52_ADCA_MIXER_VOL, + CS42L52_ADCB_MIXER_VOL, 7, 1, 1), + + SOC_DOUBLE_R_SX_TLV("PGA Volume", CS42L52_PGAA_CTL, + CS42L52_PGAB_CTL, 0, 0x28, 0x30, pga_tlv), + + SOC_DOUBLE_R_SX_TLV("PCM Mixer Volume", + CS42L52_PCMA_MIXER_VOL, CS42L52_PCMB_MIXER_VOL, + 6, 0x7f, 0x19, hl_tlv), + SOC_DOUBLE_R("PCM Mixer Switch", + CS42L52_PCMA_MIXER_VOL, CS42L52_PCMB_MIXER_VOL, 7, 1, 1), + + SOC_ENUM("Beep Config", beep_config_enum), + SOC_ENUM("Beep Pitch", beep_pitch_enum), + SOC_ENUM("Beep on Time", beep_ontime_enum), + SOC_ENUM("Beep off Time", beep_offtime_enum), + SOC_SINGLE_TLV("Beep Volume", CS42L52_BEEP_VOL, 0, 0x1f, 0x07, hl_tlv), + SOC_SINGLE("Beep Mixer Switch", CS42L52_BEEP_TONE_CTL, 5, 1, 1), + SOC_ENUM("Beep Treble Corner Freq", beep_treble_enum), + SOC_ENUM("Beep Bass Corner Freq", beep_bass_enum), + + SOC_SINGLE("Tone Control Switch", CS42L52_BEEP_TONE_CTL, 0, 1, 1), + SOC_SINGLE_TLV("Treble Gain Volume", + CS42L52_TONE_CTL, 4, 15, 1, hl_tlv), + SOC_SINGLE_TLV("Bass Gain Volume", + CS42L52_TONE_CTL, 0, 15, 1, hl_tlv), + + /* Limiter */ + SOC_SINGLE_TLV("Limiter Max Threshold Volume", + CS42L52_LIMITER_CTL1, 5, 7, 0, limiter_tlv), + SOC_SINGLE_TLV("Limiter Cushion Threshold Volume", + CS42L52_LIMITER_CTL1, 2, 7, 0, limiter_tlv), + SOC_SINGLE_TLV("Limiter Release Rate Volume", + CS42L52_LIMITER_CTL2, 0, 63, 0, limiter_tlv), + SOC_SINGLE_TLV("Limiter Attack Rate Volume", + CS42L52_LIMITER_AT_RATE, 0, 63, 0, limiter_tlv), + + SOC_SINGLE("Limiter SR Switch", CS42L52_LIMITER_CTL1, 1, 1, 0), + SOC_SINGLE("Limiter ZC Switch", CS42L52_LIMITER_CTL1, 0, 1, 0), + SOC_SINGLE("Limiter Switch", CS42L52_LIMITER_CTL2, 7, 1, 0), + + /* ALC */ + SOC_SINGLE_TLV("ALC Attack Rate Volume", CS42L52_ALC_CTL, + 0, 63, 0, limiter_tlv), + SOC_SINGLE_TLV("ALC Release Rate Volume", CS42L52_ALC_RATE, + 0, 63, 0, limiter_tlv), + SOC_SINGLE_TLV("ALC Max Threshold Volume", CS42L52_ALC_THRESHOLD, + 5, 7, 0, limiter_tlv), + SOC_SINGLE_TLV("ALC Min Threshold Volume", CS42L52_ALC_THRESHOLD, + 2, 7, 0, limiter_tlv), + + SOC_DOUBLE_R("ALC SR Capture Switch", CS42L52_PGAA_CTL, + CS42L52_PGAB_CTL, 7, 1, 1), + SOC_DOUBLE_R("ALC ZC Capture Switch", CS42L52_PGAA_CTL, + CS42L52_PGAB_CTL, 6, 1, 1), + SOC_DOUBLE("ALC Capture Switch", CS42L52_ALC_CTL, 6, 7, 1, 0), + + /* Noise gate */ + SOC_ENUM("NG Type Switch", ng_type_enum), + SOC_SINGLE("NG Enable Switch", CS42L52_NOISE_GATE_CTL, 6, 1, 0), + SOC_SINGLE("NG Boost Switch", CS42L52_NOISE_GATE_CTL, 5, 1, 1), + SOC_ENUM("NG Threshold", ng_threshold_enum), + SOC_ENUM("NG Delay", ng_delay_enum), + + SOC_DOUBLE("HPF Switch", CS42L52_ANALOG_HPF_CTL, 5, 7, 1, 0), + + SOC_DOUBLE("Analog SR Switch", CS42L52_ANALOG_HPF_CTL, 1, 3, 1, 1), + SOC_DOUBLE("Analog ZC Switch", CS42L52_ANALOG_HPF_CTL, 0, 2, 1, 1), + SOC_SINGLE("Digital SR Switch", CS42L52_MISC_CTL, 1, 1, 0), + SOC_SINGLE("Digital ZC Switch", CS42L52_MISC_CTL, 0, 1, 0), + SOC_SINGLE("Deemphasis Switch", CS42L52_MISC_CTL, 2, 1, 0), + + SOC_SINGLE("Batt Compensation Switch", CS42L52_BATT_COMPEN, 7, 1, 0), + SOC_SINGLE("Batt VP Monitor Switch", CS42L52_BATT_COMPEN, 6, 1, 0), + SOC_SINGLE("Batt VP ref", CS42L52_BATT_COMPEN, 0, 0x0f, 0), + + SOC_SINGLE("PGA AIN1L Switch", CS42L52_ADC_PGA_A, 0, 1, 0), + SOC_SINGLE("PGA AIN1R Switch", CS42L52_ADC_PGA_B, 0, 1, 0), + SOC_SINGLE("PGA AIN2L Switch", CS42L52_ADC_PGA_A, 1, 1, 0), + SOC_SINGLE("PGA AIN2R Switch", CS42L52_ADC_PGA_B, 1, 1, 0), + + SOC_SINGLE("PGA AIN3L Switch", CS42L52_ADC_PGA_A, 2, 1, 0), + SOC_SINGLE("PGA AIN3R Switch", CS42L52_ADC_PGA_B, 2, 1, 0), + + SOC_SINGLE("PGA AIN4L Switch", CS42L52_ADC_PGA_A, 3, 1, 0), + SOC_SINGLE("PGA AIN4R Switch", CS42L52_ADC_PGA_B, 3, 1, 0), + + SOC_SINGLE("PGA MICA Switch", CS42L52_ADC_PGA_A, 4, 1, 0), + SOC_SINGLE("PGA MICB Switch", CS42L52_ADC_PGA_B, 4, 1, 0), + +}; + +static const struct snd_soc_dapm_widget cs42l52_dapm_widgets[] = { + + SND_SOC_DAPM_INPUT("AIN1L"), + SND_SOC_DAPM_INPUT("AIN1R"), + SND_SOC_DAPM_INPUT("AIN2L"), + SND_SOC_DAPM_INPUT("AIN2R"), + SND_SOC_DAPM_INPUT("AIN3L"), + SND_SOC_DAPM_INPUT("AIN3R"), + SND_SOC_DAPM_INPUT("AIN4L"), + SND_SOC_DAPM_INPUT("AIN4R"), + SND_SOC_DAPM_INPUT("MICA"), + SND_SOC_DAPM_INPUT("MICB"), + SND_SOC_DAPM_SIGGEN("Beep"), + + SND_SOC_DAPM_AIF_OUT("AIFOUTL", NULL, 0, + SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("AIFOUTR", NULL, 0, + SND_SOC_NOPM, 0, 0), + + SND_SOC_DAPM_MUX("MICA Mux", SND_SOC_NOPM, 0, 0, &mica_mux), + SND_SOC_DAPM_MUX("MICB Mux", SND_SOC_NOPM, 0, 0, &micb_mux), + + SND_SOC_DAPM_ADC("ADC Left", NULL, CS42L52_PWRCTL1, 1, 1), + SND_SOC_DAPM_ADC("ADC Right", NULL, CS42L52_PWRCTL1, 2, 1), + SND_SOC_DAPM_PGA("PGA Left", CS42L52_PWRCTL1, 3, 1, NULL, 0), + SND_SOC_DAPM_PGA("PGA Right", CS42L52_PWRCTL1, 4, 1, NULL, 0), + + SND_SOC_DAPM_MUX("ADC Left Mux", SND_SOC_NOPM, 0, 0, &adca_mux), + SND_SOC_DAPM_MUX("ADC Right Mux", SND_SOC_NOPM, 0, 0, &adcb_mux), + + SND_SOC_DAPM_MUX("ADC Left Swap", SND_SOC_NOPM, + 0, 0, &adca_mixer), + SND_SOC_DAPM_MUX("ADC Right Swap", SND_SOC_NOPM, + 0, 0, &adcb_mixer), + + SND_SOC_DAPM_MUX("Output Mux", SND_SOC_NOPM, + 0, 0, &digital_output_mux), + + SND_SOC_DAPM_PGA("PGA MICA", CS42L52_PWRCTL2, 1, 1, NULL, 0), + SND_SOC_DAPM_PGA("PGA MICB", CS42L52_PWRCTL2, 2, 1, NULL, 0), + + SND_SOC_DAPM_SUPPLY("Mic Bias", CS42L52_PWRCTL2, 0, 1, NULL, 0), + SND_SOC_DAPM_SUPPLY("Charge Pump", CS42L52_PWRCTL1, 7, 1, NULL, 0), + + SND_SOC_DAPM_AIF_IN("AIFINL", NULL, 0, + SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("AIFINR", NULL, 0, + SND_SOC_NOPM, 0, 0), + + SND_SOC_DAPM_DAC("DAC Left", NULL, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_DAC("DAC Right", NULL, SND_SOC_NOPM, 0, 0), + + SND_SOC_DAPM_SWITCH("Bypass Left", CS42L52_MISC_CTL, + 6, 0, &passthrul_ctl), + SND_SOC_DAPM_SWITCH("Bypass Right", CS42L52_MISC_CTL, + 7, 0, &passthrur_ctl), + + SND_SOC_DAPM_MUX("PCM Left Swap", SND_SOC_NOPM, + 0, 0, &pcma_mixer), + SND_SOC_DAPM_MUX("PCM Right Swap", SND_SOC_NOPM, + 0, 0, &pcmb_mixer), + + SND_SOC_DAPM_SWITCH("HP Left Amp", SND_SOC_NOPM, 0, 0, &hpl_ctl), + SND_SOC_DAPM_SWITCH("HP Right Amp", SND_SOC_NOPM, 0, 0, &hpr_ctl), + + SND_SOC_DAPM_SWITCH("SPK Left Amp", SND_SOC_NOPM, 0, 0, &spkl_ctl), + SND_SOC_DAPM_SWITCH("SPK Right Amp", SND_SOC_NOPM, 0, 0, &spkr_ctl), + + SND_SOC_DAPM_OUTPUT("HPOUTA"), + SND_SOC_DAPM_OUTPUT("HPOUTB"), + SND_SOC_DAPM_OUTPUT("SPKOUTA"), + SND_SOC_DAPM_OUTPUT("SPKOUTB"), + +}; + +static const struct snd_soc_dapm_route cs42l52_audio_map[] = { + + {"Capture", NULL, "AIFOUTL"}, + {"Capture", NULL, "AIFOUTL"}, + + {"AIFOUTL", NULL, "Output Mux"}, + {"AIFOUTR", NULL, "Output Mux"}, + + {"Output Mux", "ADC", "ADC Left"}, + {"Output Mux", "ADC", "ADC Right"}, + + {"ADC Left", NULL, "Charge Pump"}, + {"ADC Right", NULL, "Charge Pump"}, + + {"Charge Pump", NULL, "ADC Left Mux"}, + {"Charge Pump", NULL, "ADC Right Mux"}, + + {"ADC Left Mux", "Input1A", "AIN1L"}, + {"ADC Right Mux", "Input1B", "AIN1R"}, + {"ADC Left Mux", "Input2A", "AIN2L"}, + {"ADC Right Mux", "Input2B", "AIN2R"}, + {"ADC Left Mux", "Input3A", "AIN3L"}, + {"ADC Right Mux", "Input3B", "AIN3R"}, + {"ADC Left Mux", "Input4A", "AIN4L"}, + {"ADC Right Mux", "Input4B", "AIN4R"}, + {"ADC Left Mux", "PGA Input Left", "PGA Left"}, + {"ADC Right Mux", "PGA Input Right" , "PGA Right"}, + + {"PGA Left", "Switch", "AIN1L"}, + {"PGA Right", "Switch", "AIN1R"}, + {"PGA Left", "Switch", "AIN2L"}, + {"PGA Right", "Switch", "AIN2R"}, + {"PGA Left", "Switch", "AIN3L"}, + {"PGA Right", "Switch", "AIN3R"}, + {"PGA Left", "Switch", "AIN4L"}, + {"PGA Right", "Switch", "AIN4R"}, + + {"PGA Left", "Switch", "PGA MICA"}, + {"PGA MICA", NULL, "MICA"}, + + {"PGA Right", "Switch", "PGA MICB"}, + {"PGA MICB", NULL, "MICB"}, + + {"HPOUTA", NULL, "HP Left Amp"}, + {"HPOUTB", NULL, "HP Right Amp"}, + {"HP Left Amp", NULL, "Bypass Left"}, + {"HP Right Amp", NULL, "Bypass Right"}, + {"Bypass Left", "Switch", "PGA Left"}, + {"Bypass Right", "Switch", "PGA Right"}, + {"HP Left Amp", "Switch", "DAC Left"}, + {"HP Right Amp", "Switch", "DAC Right"}, + + {"SPKOUTA", NULL, "SPK Left Amp"}, + {"SPKOUTB", NULL, "SPK Right Amp"}, + + {"SPK Left Amp", NULL, "Beep"}, + {"SPK Right Amp", NULL, "Beep"}, + {"SPK Left Amp", "Switch", "Playback"}, + {"SPK Right Amp", "Switch", "Playback"}, + + {"DAC Left", NULL, "Beep"}, + {"DAC Right", NULL, "Beep"}, + {"DAC Left", NULL, "Playback"}, + {"DAC Right", NULL, "Playback"}, + + {"Output Mux", "DSP", "Playback"}, + {"Output Mux", "DSP", "Playback"}, + + {"AIFINL", NULL, "Playback"}, + {"AIFINR", NULL, "Playback"}, + +}; + +struct cs42l52_clk_para { + u32 mclk; + u32 rate; + u8 speed; + u8 group; + u8 videoclk; + u8 ratio; + u8 mclkdiv2; +}; + +static const struct cs42l52_clk_para clk_map_table[] = { + /*8k*/ + {12288000, 8000, CLK_QS_MODE, CLK_32K, CLK_NO_27M, CLK_R_128, 0}, + {18432000, 8000, CLK_QS_MODE, CLK_32K, CLK_NO_27M, CLK_R_128, 0}, + {12000000, 8000, CLK_QS_MODE, CLK_32K, CLK_NO_27M, CLK_R_125, 0}, + {24000000, 8000, CLK_QS_MODE, CLK_32K, CLK_NO_27M, CLK_R_125, 1}, + {27000000, 8000, CLK_QS_MODE, CLK_32K, CLK_27M_MCLK, CLK_R_125, 0}, + + /*11.025k*/ + {11289600, 11025, CLK_QS_MODE, CLK_NO_32K, CLK_NO_27M, CLK_R_128, 0}, + {16934400, 11025, CLK_QS_MODE, CLK_NO_32K, CLK_NO_27M, CLK_R_128, 0}, + + /*16k*/ + {12288000, 16000, CLK_HS_MODE, CLK_32K, CLK_NO_27M, CLK_R_128, 0}, + {18432000, 16000, CLK_HS_MODE, CLK_32K, CLK_NO_27M, CLK_R_128, 0}, + {12000000, 16000, CLK_HS_MODE, CLK_32K, CLK_NO_27M, CLK_R_125, 0}, + {24000000, 16000, CLK_HS_MODE, CLK_32K, CLK_NO_27M, CLK_R_125, 1}, + {27000000, 16000, CLK_HS_MODE, CLK_32K, CLK_27M_MCLK, CLK_R_125, 1}, + + /*22.05k*/ + {11289600, 22050, CLK_HS_MODE, CLK_NO_32K, CLK_NO_27M, CLK_R_128, 0}, + {16934400, 22050, CLK_HS_MODE, CLK_NO_32K, CLK_NO_27M, CLK_R_128, 0}, + + /* 32k */ + {12288000, 32000, CLK_SS_MODE, CLK_32K, CLK_NO_27M, CLK_R_128, 0}, + {18432000, 32000, CLK_SS_MODE, CLK_32K, CLK_NO_27M, CLK_R_128, 0}, + {12000000, 32000, CLK_SS_MODE, CLK_32K, CLK_NO_27M, CLK_R_125, 0}, + {24000000, 32000, CLK_SS_MODE, CLK_32K, CLK_NO_27M, CLK_R_125, 1}, + {27000000, 32000, CLK_SS_MODE, CLK_32K, CLK_27M_MCLK, CLK_R_125, 0}, + + /* 44.1k */ + {11289600, 44100, CLK_SS_MODE, CLK_NO_32K, CLK_NO_27M, CLK_R_128, 0}, + {16934400, 44100, CLK_SS_MODE, CLK_NO_32K, CLK_NO_27M, CLK_R_128, 0}, + + /* 48k */ + {12288000, 48000, CLK_SS_MODE, CLK_NO_32K, CLK_NO_27M, CLK_R_128, 0}, + {18432000, 48000, CLK_SS_MODE, CLK_NO_32K, CLK_NO_27M, CLK_R_128, 0}, + {12000000, 48000, CLK_SS_MODE, CLK_NO_32K, CLK_NO_27M, CLK_R_125, 0}, + {24000000, 48000, CLK_SS_MODE, CLK_NO_32K, CLK_NO_27M, CLK_R_125, 1}, + {27000000, 48000, CLK_SS_MODE, CLK_NO_32K, CLK_27M_MCLK, CLK_R_125, 1}, + + /* 88.2k */ + {11289600, 88200, CLK_DS_MODE, CLK_NO_32K, CLK_NO_27M, CLK_R_128, 0}, + {16934400, 88200, CLK_DS_MODE, CLK_NO_32K, CLK_NO_27M, CLK_R_128, 0}, + + /* 96k */ + {12288000, 96000, CLK_DS_MODE, CLK_NO_32K, CLK_NO_27M, CLK_R_128, 0}, + {18432000, 96000, CLK_DS_MODE, CLK_NO_32K, CLK_NO_27M, CLK_R_128, 0}, + {12000000, 96000, CLK_DS_MODE, CLK_NO_32K, CLK_NO_27M, CLK_R_125, 0}, + {24000000, 96000, CLK_DS_MODE, CLK_NO_32K, CLK_NO_27M, CLK_R_125, 1}, +}; + +static int cs42l52_get_clk(int mclk, int rate) +{ + int i, ret = 0; + u_int mclk1, mclk2 = 0; + + for (i = 0; i < ARRAY_SIZE(clk_map_table); i++) { + if (clk_map_table[i].rate == rate) { + mclk1 = clk_map_table[i].mclk; + if (abs(mclk - mclk1) < abs(mclk - mclk2)) { + mclk2 = mclk1; + ret = i; + } + } + } + if (ret > ARRAY_SIZE(clk_map_table)) + return -EINVAL; + return ret; +} + +static int cs42l52_set_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct cs42l52_private *cs42l52 = snd_soc_codec_get_drvdata(codec); + + if ((freq >= CS42L52_MIN_CLK) && (freq <= CS42L52_MAX_CLK)) { + cs42l52->sysclk = freq; + } else { + dev_err(codec->dev, "Invalid freq paramter\n"); + return -EINVAL; + } + return 0; +} + +static int cs42l52_set_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct cs42l52_private *cs42l52 = snd_soc_codec_get_drvdata(codec); + int ret = 0; + u8 iface = 0; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + iface = CS42L52_IFACE_CTL1_MASTER; + break; + case SND_SOC_DAIFMT_CBS_CFS: + iface = CS42L52_IFACE_CTL1_SLAVE; + break; + default: + return -EINVAL; + } + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + iface |= CS42L52_IFACE_CTL1_ADC_FMT_I2S | + CS42L52_IFACE_CTL1_DAC_FMT_I2S; + break; + case SND_SOC_DAIFMT_RIGHT_J: + iface |= CS42L52_IFACE_CTL1_DAC_FMT_RIGHT_J; + break; + case SND_SOC_DAIFMT_LEFT_J: + iface |= CS42L52_IFACE_CTL1_ADC_FMT_LEFT_J | + CS42L52_IFACE_CTL1_DAC_FMT_LEFT_J; + break; + case SND_SOC_DAIFMT_DSP_A: + iface |= CS42L52_IFACE_CTL1_DSP_MODE_EN; + break; + case SND_SOC_DAIFMT_DSP_B: + break; + default: + return -EINVAL; + } + + /* clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + iface |= CS42L52_IFACE_CTL1_INV_SCLK; + break; + case SND_SOC_DAIFMT_IB_NF: + iface |= CS42L52_IFACE_CTL1_INV_SCLK; + break; + case SND_SOC_DAIFMT_NB_IF: + break; + default: + ret = -EINVAL; + } + cs42l52->config.format = iface; + snd_soc_write(codec, CS42L52_IFACE_CTL1, cs42l52->config.format); + + return 0; +} + +static int cs42l52_digital_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + + if (mute) + snd_soc_update_bits(codec, CS42L52_PB_CTL1, + CS42L52_PB_CTL1_MUTE_MASK, + CS42L52_PB_CTL1_MUTE); + else + snd_soc_update_bits(codec, CS42L52_PB_CTL1, + CS42L52_PB_CTL1_MUTE_MASK, + CS42L52_PB_CTL1_UNMUTE); + + return 0; +} + +static int cs42l52_pcm_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 cs42l52_private *cs42l52 = snd_soc_codec_get_drvdata(codec); + u32 clk = 0; + int index; + + index = cs42l52_get_clk(cs42l52->sysclk, params_rate(params)); + if (index >= 0) { + cs42l52->sysclk = clk_map_table[index].mclk; + + clk |= (clk_map_table[index].speed << CLK_SPEED_SHIFT) | + (clk_map_table[index].group << CLK_32K_SR_SHIFT) | + (clk_map_table[index].videoclk << CLK_27M_MCLK_SHIFT) | + (clk_map_table[index].ratio << CLK_RATIO_SHIFT) | + clk_map_table[index].mclkdiv2; + + snd_soc_write(codec, CS42L52_CLK_CTL, clk); + } else { + dev_err(codec->dev, "can't get correct mclk\n"); + return -EINVAL; + } + + return 0; +} + +static int cs42l52_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + struct cs42l52_private *cs42l52 = snd_soc_codec_get_drvdata(codec); + + switch (level) { + case SND_SOC_BIAS_ON: + break; + case SND_SOC_BIAS_PREPARE: + snd_soc_update_bits(codec, CS42L52_PWRCTL1, + CS42L52_PWRCTL1_PDN_CODEC, 0); + break; + case SND_SOC_BIAS_STANDBY: + if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) { + regcache_cache_only(cs42l52->regmap, false); + regcache_sync(cs42l52->regmap); + } + snd_soc_write(codec, CS42L52_PWRCTL1, CS42L52_PWRCTL1_PDN_ALL); + break; + case SND_SOC_BIAS_OFF: + snd_soc_write(codec, CS42L52_PWRCTL1, CS42L52_PWRCTL1_PDN_ALL); + regcache_cache_only(cs42l52->regmap, true); + break; + } + codec->dapm.bias_level = level; + + return 0; +} + +#define CS42L52_RATES (SNDRV_PCM_RATE_8000_96000) + +#define CS42L52_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE | \ + SNDRV_PCM_FMTBIT_S18_3LE | SNDRV_PCM_FMTBIT_U18_3LE | \ + SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_U20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_U24_LE) + +static struct snd_soc_dai_ops cs42l52_ops = { + .hw_params = cs42l52_pcm_hw_params, + .digital_mute = cs42l52_digital_mute, + .set_fmt = cs42l52_set_fmt, + .set_sysclk = cs42l52_set_sysclk, +}; + +static struct snd_soc_dai_driver cs42l52_dai = { + .name = "cs42l52", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = CS42L52_RATES, + .formats = CS42L52_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = CS42L52_RATES, + .formats = CS42L52_FORMATS, + }, + .ops = &cs42l52_ops, +}; + +static int cs42l52_suspend(struct snd_soc_codec *codec) +{ + cs42l52_set_bias_level(codec, SND_SOC_BIAS_OFF); + + return 0; +} + +static int cs42l52_resume(struct snd_soc_codec *codec) +{ + cs42l52_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + return 0; +} + +#if defined(CONFIG_INPUT) || defined(CONFIG_INPUT_MODULE) +static int beep_rates[] = { + 261, 522, 585, 667, 706, 774, 889, 1000, + 1043, 1200, 1333, 1412, 1600, 1714, 2000, 2182 +}; + +static void cs42l52_beep_work(struct work_struct *work) +{ + struct cs42l52_private *cs42l52 = + container_of(work, struct cs42l52_private, beep_work); + struct snd_soc_codec *codec = cs42l52->codec; + struct snd_soc_dapm_context *dapm = &codec->dapm; + int i; + int val = 0; + int best = 0; + + if (cs42l52->beep_rate) { + for (i = 0; i < ARRAY_SIZE(beep_rates); i++) { + if (abs(cs42l52->beep_rate - beep_rates[i]) < + abs(cs42l52->beep_rate - beep_rates[best])) + best = i; + } + + dev_dbg(codec->dev, "Set beep rate %dHz for requested %dHz\n", + beep_rates[best], cs42l52->beep_rate); + + val = (best << CS42L52_BEEP_RATE_SHIFT); + + snd_soc_dapm_enable_pin(dapm, "Beep"); + } else { + dev_dbg(codec->dev, "Disabling beep\n"); + snd_soc_dapm_disable_pin(dapm, "Beep"); + } + + snd_soc_update_bits(codec, CS42L52_BEEP_FREQ, + CS42L52_BEEP_RATE_MASK, val); + + snd_soc_dapm_sync(dapm); +} + +/* For usability define a way of injecting beep events for the device - + * many systems will not have a keyboard. + */ +static int cs42l52_beep_event(struct input_dev *dev, unsigned int type, + unsigned int code, int hz) +{ + struct snd_soc_codec *codec = input_get_drvdata(dev); + struct cs42l52_private *cs42l52 = snd_soc_codec_get_drvdata(codec); + + dev_dbg(codec->dev, "Beep event %x %x\n", code, hz); + + switch (code) { + case SND_BELL: + if (hz) + hz = 261; + case SND_TONE: + break; + default: + return -1; + } + + /* Kick the beep from a workqueue */ + cs42l52->beep_rate = hz; + schedule_work(&cs42l52->beep_work); + return 0; +} + +static ssize_t cs42l52_beep_set(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct cs42l52_private *cs42l52 = dev_get_drvdata(dev); + long int time; + int ret; + + ret = kstrtol(buf, 10, &time); + if (ret != 0) + return ret; + + input_event(cs42l52->beep, EV_SND, SND_TONE, time); + + return count; +} + +static DEVICE_ATTR(beep, 0200, NULL, cs42l52_beep_set); + +static void cs42l52_init_beep(struct snd_soc_codec *codec) +{ + struct cs42l52_private *cs42l52 = snd_soc_codec_get_drvdata(codec); + int ret; + + cs42l52->beep = input_allocate_device(); + if (!cs42l52->beep) { + dev_err(codec->dev, "Failed to allocate beep device\n"); + return; + } + + INIT_WORK(&cs42l52->beep_work, cs42l52_beep_work); + cs42l52->beep_rate = 0; + + cs42l52->beep->name = "CS42L52 Beep Generator"; + cs42l52->beep->phys = dev_name(codec->dev); + cs42l52->beep->id.bustype = BUS_I2C; + + cs42l52->beep->evbit[0] = BIT_MASK(EV_SND); + cs42l52->beep->sndbit[0] = BIT_MASK(SND_BELL) | BIT_MASK(SND_TONE); + cs42l52->beep->event = cs42l52_beep_event; + cs42l52->beep->dev.parent = codec->dev; + input_set_drvdata(cs42l52->beep, codec); + + ret = input_register_device(cs42l52->beep); + if (ret != 0) { + input_free_device(cs42l52->beep); + cs42l52->beep = NULL; + dev_err(codec->dev, "Failed to register beep device\n"); + } + + ret = device_create_file(codec->dev, &dev_attr_beep); + if (ret != 0) { + dev_err(codec->dev, "Failed to create keyclick file: %d\n", + ret); + } +} + +static void cs42l52_free_beep(struct snd_soc_codec *codec) +{ + struct cs42l52_private *cs42l52 = snd_soc_codec_get_drvdata(codec); + + device_remove_file(codec->dev, &dev_attr_beep); + input_unregister_device(cs42l52->beep); + cancel_work_sync(&cs42l52->beep_work); + cs42l52->beep = NULL; + + snd_soc_update_bits(codec, CS42L52_BEEP_TONE_CTL, + CS42L52_BEEP_EN_MASK, 0); +} +#else +static void cs42l52_init_beep(struct snd_soc_codec *codec) +{ +} + +static void cs42l52_free_beep(struct snd_soc_codec *codec) +{ +} +#endif + +static int cs42l52_probe(struct snd_soc_codec *codec) +{ + struct cs42l52_private *cs42l52 = snd_soc_codec_get_drvdata(codec); + int ret; + + codec->control_data = cs42l52->regmap; + ret = snd_soc_codec_set_cache_io(codec, 8, 8, SND_SOC_REGMAP); + if (ret < 0) { + dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret); + return ret; + } + regcache_cache_only(cs42l52->regmap, true); + + cs42l52_init_beep(codec); + + cs42l52_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + cs42l52->sysclk = CS42L52_DEFAULT_CLK; + cs42l52->config.format = CS42L52_DEFAULT_FORMAT; + + /* Set Platform MICx CFG */ + snd_soc_update_bits(codec, CS42L52_MICA_CTL, + CS42L52_MIC_CTL_TYPE_MASK, + cs42l52->pdata.mica_cfg << + CS42L52_MIC_CTL_TYPE_SHIFT); + + snd_soc_update_bits(codec, CS42L52_MICB_CTL, + CS42L52_MIC_CTL_TYPE_MASK, + cs42l52->pdata.micb_cfg << + CS42L52_MIC_CTL_TYPE_SHIFT); + + /* if Single Ended, Get Mic_Select */ + if (cs42l52->pdata.mica_cfg) + snd_soc_update_bits(codec, CS42L52_MICA_CTL, + CS42L52_MIC_CTL_MIC_SEL_MASK, + cs42l52->pdata.mica_sel << + CS42L52_MIC_CTL_MIC_SEL_SHIFT); + if (cs42l52->pdata.micb_cfg) + snd_soc_update_bits(codec, CS42L52_MICB_CTL, + CS42L52_MIC_CTL_MIC_SEL_MASK, + cs42l52->pdata.micb_sel << + CS42L52_MIC_CTL_MIC_SEL_SHIFT); + + /* Set Platform Charge Pump Freq */ + snd_soc_update_bits(codec, CS42L52_CHARGE_PUMP, + CS42L52_CHARGE_PUMP_MASK, + cs42l52->pdata.chgfreq << + CS42L52_CHARGE_PUMP_SHIFT); + + /* Set Platform Bias Level */ + snd_soc_update_bits(codec, CS42L52_IFACE_CTL2, + CS42L52_IFACE_CTL2_BIAS_LVL, + cs42l52->pdata.micbias_lvl); + + return ret; +} + +static int cs42l52_remove(struct snd_soc_codec *codec) +{ + cs42l52_free_beep(codec); + cs42l52_set_bias_level(codec, SND_SOC_BIAS_OFF); + + return 0; +} + +static struct snd_soc_codec_driver soc_codec_dev_cs42l52 = { + .probe = cs42l52_probe, + .remove = cs42l52_remove, + .suspend = cs42l52_suspend, + .resume = cs42l52_resume, + .set_bias_level = cs42l52_set_bias_level, + + .dapm_widgets = cs42l52_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(cs42l52_dapm_widgets), + .dapm_routes = cs42l52_audio_map, + .num_dapm_routes = ARRAY_SIZE(cs42l52_audio_map), + + .controls = cs42l52_snd_controls, + .num_controls = ARRAY_SIZE(cs42l52_snd_controls), +}; + +/* Current and threshold powerup sequence Pg37 */ +static const struct reg_default cs42l52_threshold_patch[] = { + + { 0x00, 0x99 }, + { 0x3E, 0xBA }, + { 0x47, 0x80 }, + { 0x32, 0xBB }, + { 0x32, 0x3B }, + { 0x00, 0x00 }, + +}; + +static struct regmap_config cs42l52_regmap = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = CS42L52_MAX_REGISTER, + .reg_defaults = cs42l52_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(cs42l52_reg_defaults), + .readable_reg = cs42l52_readable_register, + .volatile_reg = cs42l52_volatile_register, + .cache_type = REGCACHE_RBTREE, +}; + +static int cs42l52_i2c_probe(struct i2c_client *i2c_client, + const struct i2c_device_id *id) +{ + struct cs42l52_private *cs42l52; + int ret; + unsigned int devid = 0; + unsigned int reg; + + cs42l52 = devm_kzalloc(&i2c_client->dev, sizeof(struct cs42l52_private), + GFP_KERNEL); + if (cs42l52 == NULL) + return -ENOMEM; + cs42l52->dev = &i2c_client->dev; + + cs42l52->regmap = regmap_init_i2c(i2c_client, &cs42l52_regmap); + if (IS_ERR(cs42l52->regmap)) { + ret = PTR_ERR(cs42l52->regmap); + dev_err(&i2c_client->dev, "regmap_init() failed: %d\n", ret); + goto err; + } + + i2c_set_clientdata(i2c_client, cs42l52); + + if (dev_get_platdata(&i2c_client->dev)) + memcpy(&cs42l52->pdata, dev_get_platdata(&i2c_client->dev), + sizeof(cs42l52->pdata)); + + ret = regmap_register_patch(cs42l52->regmap, cs42l52_threshold_patch, + ARRAY_SIZE(cs42l52_threshold_patch)); + if (ret != 0) + dev_warn(cs42l52->dev, "Failed to apply regmap patch: %d\n", + ret); + + ret = regmap_read(cs42l52->regmap, CS42L52_CHIP, ®); + devid = reg & CS42L52_CHIP_ID_MASK; + if (devid != CS42L52_CHIP_ID) { + ret = -ENODEV; + dev_err(&i2c_client->dev, + "CS42L52 Device ID (%X). Expected %X\n", + devid, CS42L52_CHIP_ID); + goto err_regmap; + } + + regcache_cache_only(cs42l52->regmap, true); + + ret = snd_soc_register_codec(&i2c_client->dev, + &soc_codec_dev_cs42l52, &cs42l52_dai, 1); + if (ret < 0) + goto err_regmap; + return 0; + +err_regmap: + regmap_exit(cs42l52->regmap); + +err: + return ret; +} + +static int cs42l52_i2c_remove(struct i2c_client *client) +{ + struct cs42l52_private *cs42l52 = i2c_get_clientdata(client); + + snd_soc_unregister_codec(&client->dev); + regmap_exit(cs42l52->regmap); + + return 0; +} + +static const struct i2c_device_id cs42l52_id[] = { + { "cs42l52", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, cs42l52_id); + +static struct i2c_driver cs42l52_i2c_driver = { + .driver = { + .name = "cs42l52", + .owner = THIS_MODULE, + }, + .id_table = cs42l52_id, + .probe = cs42l52_i2c_probe, + .remove = __devexit_p(cs42l52_i2c_remove), +}; + +module_i2c_driver(cs42l52_i2c_driver); + +MODULE_DESCRIPTION("ASoC CS42L52 driver"); +MODULE_AUTHOR("Georgi Vlaev, Nucleus Systems Ltd, <joe@nucleusys.com>"); +MODULE_AUTHOR("Brian Austin, Cirrus Logic Inc, <brian.austin@cirrus.com>"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/cs42l52.h b/sound/soc/codecs/cs42l52.h new file mode 100644 index 00000000000..60985c05907 --- /dev/null +++ b/sound/soc/codecs/cs42l52.h @@ -0,0 +1,274 @@ +/* + * cs42l52.h -- CS42L52 ALSA SoC audio driver + * + * Copyright 2012 CirrusLogic, Inc. + * + * Author: Georgi Vlaev <joe@nucleusys.com> + * Author: Brian Austin <brian.austin@cirrus.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 __CS42L52_H__ +#define __CS42L52_H__ + +#define CS42L52_NAME "CS42L52" +#define CS42L52_DEFAULT_CLK 12000000 +#define CS42L52_MIN_CLK 11000000 +#define CS42L52_MAX_CLK 27000000 +#define CS42L52_DEFAULT_FORMAT SNDRV_PCM_FMTBIT_S16_LE +#define CS42L52_DEFAULT_MAX_CHANS 2 +#define CS42L52_SYSCLK 1 + +#define CS42L52_CHIP_SWICTH (1 << 17) +#define CS42L52_ALL_IN_ONE (1 << 16) +#define CS42L52_CHIP_ONE 0x00 +#define CS42L52_CHIP_TWO 0x01 +#define CS42L52_CHIP_THR 0x02 +#define CS42L52_CHIP_MASK 0x0f + +#define CS42L52_FIX_BITS_CTL 0x00 +#define CS42L52_CHIP 0x01 +#define CS42L52_CHIP_ID 0xE0 +#define CS42L52_CHIP_ID_MASK 0xF8 +#define CS42L52_CHIP_REV_A0 0x00 +#define CS42L52_CHIP_REV_A1 0x01 +#define CS42L52_CHIP_REV_B0 0x02 +#define CS42L52_CHIP_REV_MASK 0x03 + +#define CS42L52_PWRCTL1 0x02 +#define CS42L52_PWRCTL1_PDN_ALL 0x9F +#define CS42L52_PWRCTL1_PDN_CHRG 0x80 +#define CS42L52_PWRCTL1_PDN_PGAB 0x10 +#define CS42L52_PWRCTL1_PDN_PGAA 0x08 +#define CS42L52_PWRCTL1_PDN_ADCB 0x04 +#define CS42L52_PWRCTL1_PDN_ADCA 0x02 +#define CS42L52_PWRCTL1_PDN_CODEC 0x01 + +#define CS42L52_PWRCTL2 0x03 +#define CS42L52_PWRCTL2_OVRDB (1 << 4) +#define CS42L52_PWRCTL2_OVRDA (1 << 3) +#define CS42L52_PWRCTL2_PDN_MICB (1 << 2) +#define CS42L52_PWRCTL2_PDN_MICB_SHIFT 2 +#define CS42L52_PWRCTL2_PDN_MICA (1 << 1) +#define CS42L52_PWRCTL2_PDN_MICA_SHIFT 1 +#define CS42L52_PWRCTL2_PDN_MICBIAS (1 << 0) +#define CS42L52_PWRCTL2_PDN_MICBIAS_SHIFT 0 + +#define CS42L52_PWRCTL3 0x04 +#define CS42L52_PWRCTL3_HPB_PDN_SHIFT 6 +#define CS42L52_PWRCTL3_HPB_ON_LOW 0x00 +#define CS42L52_PWRCTL3_HPB_ON_HIGH 0x01 +#define CS42L52_PWRCTL3_HPB_ALWAYS_ON 0x02 +#define CS42L52_PWRCTL3_HPB_ALWAYS_OFF 0x03 +#define CS42L52_PWRCTL3_HPA_PDN_SHIFT 4 +#define CS42L52_PWRCTL3_HPA_ON_LOW 0x00 +#define CS42L52_PWRCTL3_HPA_ON_HIGH 0x01 +#define CS42L52_PWRCTL3_HPA_ALWAYS_ON 0x02 +#define CS42L52_PWRCTL3_HPA_ALWAYS_OFF 0x03 +#define CS42L52_PWRCTL3_SPKB_PDN_SHIFT 2 +#define CS42L52_PWRCTL3_SPKB_ON_LOW 0x00 +#define CS42L52_PWRCTL3_SPKB_ON_HIGH 0x01 +#define CS42L52_PWRCTL3_SPKB_ALWAYS_ON 0x02 +#define CS42L52_PWRCTL3_PDN_SPKB (1 << 2) +#define CS42L52_PWRCTL3_PDN_SPKA (1 << 0) +#define CS42L52_PWRCTL3_SPKA_PDN_SHIFT 0 +#define CS42L52_PWRCTL3_SPKA_ON_LOW 0x00 +#define CS42L52_PWRCTL3_SPKA_ON_HIGH 0x01 +#define CS42L52_PWRCTL3_SPKA_ALWAYS_ON 0x02 + +#define CS42L52_DEFAULT_OUTPUT_STATE 0x05 +#define CS42L52_PWRCTL3_CONF_MASK 0x03 + +#define CS42L52_CLK_CTL 0x05 +#define CLK_AUTODECT_ENABLE (1 << 7) +#define CLK_SPEED_SHIFT 5 +#define CLK_DS_MODE 0x00 +#define CLK_SS_MODE 0x01 +#define CLK_HS_MODE 0x02 +#define CLK_QS_MODE 0x03 +#define CLK_32K_SR_SHIFT 4 +#define CLK_32K 0x01 +#define CLK_NO_32K 0x00 +#define CLK_27M_MCLK_SHIFT 3 +#define CLK_27M_MCLK 0x01 +#define CLK_NO_27M 0x00 +#define CLK_RATIO_SHIFT 1 +#define CLK_R_128 0x00 +#define CLK_R_125 0x01 +#define CLK_R_132 0x02 +#define CLK_R_136 0x03 + +#define CS42L52_IFACE_CTL1 0x06 +#define CS42L52_IFACE_CTL1_MASTER (1 << 7) +#define CS42L52_IFACE_CTL1_SLAVE (0 << 7) +#define CS42L52_IFACE_CTL1_INV_SCLK (1 << 6) +#define CS42L52_IFACE_CTL1_ADC_FMT_I2S (1 << 5) +#define CS42L52_IFACE_CTL1_ADC_FMT_LEFT_J (0 << 5) +#define CS42L52_IFACE_CTL1_DSP_MODE_EN (1 << 4) +#define CS42L52_IFACE_CTL1_DAC_FMT_LEFT_J (0 << 2) +#define CS42L52_IFACE_CTL1_DAC_FMT_I2S (1 << 2) +#define CS42L52_IFACE_CTL1_DAC_FMT_RIGHT_J (2 << 2) +#define CS42L52_IFACE_CTL1_WL_32BIT (0x00) +#define CS42L52_IFACE_CTL1_WL_24BIT (0x01) +#define CS42L52_IFACE_CTL1_WL_20BIT (0x02) +#define CS42L52_IFACE_CTL1_WL_16BIT (0x03) +#define CS42L52_IFACE_CTL1_WL_MASK 0xFFFF + +#define CS42L52_IFACE_CTL2 0x07 +#define CS42L52_IFACE_CTL2_SC_MC_EQ (1 << 6) +#define CS42L52_IFACE_CTL2_LOOPBACK (1 << 5) +#define CS42L52_IFACE_CTL2_S_MODE_OUTPUT_EN (0 << 4) +#define CS42L52_IFACE_CTL2_S_MODE_OUTPUT_HIZ (1 << 4) +#define CS42L52_IFACE_CTL2_HP_SW_INV (1 << 3) +#define CS42L52_IFACE_CTL2_BIAS_LVL 0x07 + +#define CS42L52_ADC_PGA_A 0x08 +#define CS42L52_ADC_PGA_B 0x09 +#define CS42L52_ADC_SEL_SHIFT 5 +#define CS42L52_ADC_SEL_AIN1 0x00 +#define CS42L52_ADC_SEL_AIN2 0x01 +#define CS42L52_ADC_SEL_AIN3 0x02 +#define CS42L52_ADC_SEL_AIN4 0x03 +#define CS42L52_ADC_SEL_PGA 0x04 + +#define CS42L52_ANALOG_HPF_CTL 0x0A +#define CS42L52_HPF_CTL_ANLGSFTB (1 << 3) +#define CS42L52_HPF_CTL_ANLGSFTA (1 << 0) + +#define CS42L52_ADC_HPF_FREQ 0x0B +#define CS42L52_ADC_MISC_CTL 0x0C +#define CS42L52_ADC_MISC_CTL_SOURCE_DSP (1 << 6) + +#define CS42L52_PB_CTL1 0x0D +#define CS42L52_PB_CTL1_HP_GAIN_SHIFT 5 +#define CS42L52_PB_CTL1_HP_GAIN_03959 0x00 +#define CS42L52_PB_CTL1_HP_GAIN_04571 0x01 +#define CS42L52_PB_CTL1_HP_GAIN_05111 0x02 +#define CS42L52_PB_CTL1_HP_GAIN_06047 0x03 +#define CS42L52_PB_CTL1_HP_GAIN_07099 0x04 +#define CS42L52_PB_CTL1_HP_GAIN_08399 0x05 +#define CS42L52_PB_CTL1_HP_GAIN_10000 0x06 +#define CS42L52_PB_CTL1_HP_GAIN_11430 0x07 +#define CS42L52_PB_CTL1_INV_PCMB (1 << 3) +#define CS42L52_PB_CTL1_INV_PCMA (1 << 2) +#define CS42L52_PB_CTL1_MSTB_MUTE (1 << 1) +#define CS42L52_PB_CTL1_MSTA_MUTE (1 << 0) +#define CS42L52_PB_CTL1_MUTE_MASK 0xFFFD +#define CS42L52_PB_CTL1_MUTE 3 +#define CS42L52_PB_CTL1_UNMUTE 0 + +#define CS42L52_MISC_CTL 0x0E +#define CS42L52_MISC_CTL_DEEMPH (1 << 2) +#define CS42L52_MISC_CTL_DIGSFT (1 << 1) +#define CS42L52_MISC_CTL_DIGZC (1 << 0) + +#define CS42L52_PB_CTL2 0x0F +#define CS42L52_PB_CTL2_HPB_MUTE (1 << 7) +#define CS42L52_PB_CTL2_HPA_MUTE (1 << 6) +#define CS42L52_PB_CTL2_SPKB_MUTE (1 << 5) +#define CS42L52_PB_CTL2_SPKA_MUTE (1 << 4) +#define CS42L52_PB_CTL2_SPK_SWAP (1 << 2) +#define CS42L52_PB_CTL2_SPK_MONO (1 << 1) +#define CS42L52_PB_CTL2_SPK_MUTE50 (1 << 0) + +#define CS42L52_MICA_CTL 0x10 +#define CS42L52_MICB_CTL 0x11 +#define CS42L52_MIC_CTL_MIC_SEL_MASK 0xBF +#define CS42L52_MIC_CTL_MIC_SEL_SHIFT 6 +#define CS42L52_MIC_CTL_TYPE_MASK 0xDF +#define CS42L52_MIC_CTL_TYPE_SHIFT 5 + + +#define CS42L52_PGAA_CTL 0x12 +#define CS42L52_PGAB_CTL 0x13 +#define CS42L52_PGAX_CTL_VOL_12DB 24 +#define CS42L52_PGAX_CTL_VOL_6DB 12 /*step size 0.5db*/ + +#define CS42L52_PASSTHRUA_VOL 0x14 +#define CS42L52_PASSTHRUB_VOL 0x15 + +#define CS42L52_ADCA_VOL 0x16 +#define CS42L52_ADCB_VOL 0x17 +#define CS42L52_ADCX_VOL_24DB 24 /*step size 1db*/ +#define CS42L52_ADCX_VOL_12DB 12 +#define CS42L52_ADCX_VOL_6DB 6 + +#define CS42L52_ADCA_MIXER_VOL 0x18 +#define CS42L52_ADCB_MIXER_VOL 0x19 +#define CS42L52_ADC_MIXER_VOL_12DB 0x18 + +#define CS42L52_PCMA_MIXER_VOL 0x1A +#define CS42L52_PCMB_MIXER_VOL 0x1B + +#define CS42L52_BEEP_FREQ 0x1C +#define CS42L52_BEEP_VOL 0x1D +#define CS42L52_BEEP_TONE_CTL 0x1E +#define CS42L52_BEEP_RATE_SHIFT 4 +#define CS42L52_BEEP_RATE_MASK 0x0F + +#define CS42L52_TONE_CTL 0x1F +#define CS42L52_BEEP_EN_MASK 0x3F + +#define CS42L52_MASTERA_VOL 0x20 +#define CS42L52_MASTERB_VOL 0x21 + +#define CS42L52_HPA_VOL 0x22 +#define CS42L52_HPB_VOL 0x23 +#define CS42L52_DEFAULT_HP_VOL 0xF0 + +#define CS42L52_SPKA_VOL 0x24 +#define CS42L52_SPKB_VOL 0x25 +#define CS42L52_DEFAULT_SPK_VOL 0xF0 + +#define CS42L52_ADC_PCM_MIXER 0x26 + +#define CS42L52_LIMITER_CTL1 0x27 +#define CS42L52_LIMITER_CTL2 0x28 +#define CS42L52_LIMITER_AT_RATE 0x29 + +#define CS42L52_ALC_CTL 0x2A +#define CS42L52_ALC_CTL_ALCB_ENABLE_SHIFT 7 +#define CS42L52_ALC_CTL_ALCA_ENABLE_SHIFT 6 +#define CS42L52_ALC_CTL_FASTEST_ATTACK 0 + +#define CS42L52_ALC_RATE 0x2B +#define CS42L52_ALC_SLOWEST_RELEASE 0x3F + +#define CS42L52_ALC_THRESHOLD 0x2C +#define CS42L52_ALC_MAX_RATE_SHIFT 5 +#define CS42L52_ALC_MIN_RATE_SHIFT 2 +#define CS42L52_ALC_RATE_0DB 0 +#define CS42L52_ALC_RATE_3DB 1 +#define CS42L52_ALC_RATE_6DB 2 + +#define CS42L52_NOISE_GATE_CTL 0x2D +#define CS42L52_NG_ENABLE_SHIFT 6 +#define CS42L52_NG_THRESHOLD_SHIFT 2 +#define CS42L52_NG_MIN_70DB 2 +#define CS42L52_NG_DELAY_SHIFT 0 +#define CS42L52_NG_DELAY_100MS 1 + +#define CS42L52_CLK_STATUS 0x2E +#define CS42L52_BATT_COMPEN 0x2F + +#define CS42L52_BATT_LEVEL 0x30 +#define CS42L52_SPK_STATUS 0x31 +#define CS42L52_SPK_STATUS_PIN_SHIFT 3 +#define CS42L52_SPK_STATUS_PIN_HIGH 1 + +#define CS42L52_TEM_CTL 0x32 +#define CS42L52_TEM_CTL_SET 0x80 +#define CS42L52_THE_FOLDBACK 0x33 +#define CS42L52_CHARGE_PUMP 0x34 +#define CS42L52_CHARGE_PUMP_MASK 0xF0 +#define CS42L52_CHARGE_PUMP_SHIFT 4 +#define CS42L52_FIX_BITS1 0x3E +#define CS42L52_FIX_BITS2 0x47 + +#define CS42L52_MAX_REGISTER 0x34 + +#endif diff --git a/sound/soc/codecs/cs42l73.c b/sound/soc/codecs/cs42l73.c index 3686417f5ea..e0d45fdaa75 100644 --- a/sound/soc/codecs/cs42l73.c +++ b/sound/soc/codecs/cs42l73.c @@ -43,9 +43,6 @@ struct cs42l73_private { }; static const struct reg_default cs42l73_reg_defaults[] = { - { 1, 0x42 }, /* r01 - Device ID A&B */ - { 2, 0xA7 }, /* r02 - Device ID C&D */ - { 3, 0x30 }, /* r03 - Device ID E */ { 6, 0xF1 }, /* r06 - Power Ctl 1 */ { 7, 0xDF }, /* r07 - Power Ctl 2 */ { 8, 0x3F }, /* r08 - Power Ctl 3 */ @@ -402,37 +399,37 @@ static const struct snd_kcontrol_new ear_amp_ctl = static const struct snd_kcontrol_new cs42l73_snd_controls[] = { SOC_DOUBLE_R_SX_TLV("Headphone Analog Playback Volume", - CS42L73_HPAAVOL, CS42L73_HPBAVOL, 7, - 0xffffffC1, 0x0C, hpaloa_tlv), + CS42L73_HPAAVOL, CS42L73_HPBAVOL, 0, + 0x41, 0x4B, hpaloa_tlv), SOC_DOUBLE_R_SX_TLV("LineOut Analog Playback Volume", CS42L73_LOAAVOL, - CS42L73_LOBAVOL, 7, 0xffffffC1, 0x0C, hpaloa_tlv), + CS42L73_LOBAVOL, 0, 0x41, 0x4B, hpaloa_tlv), SOC_DOUBLE_R_SX_TLV("Input PGA Analog Volume", CS42L73_MICAPREPGAAVOL, - CS42L73_MICBPREPGABVOL, 5, 0xffffff35, - 0x34, micpga_tlv), + CS42L73_MICBPREPGABVOL, 5, 0x34, + 0x24, micpga_tlv), SOC_DOUBLE_R("MIC Preamp Switch", CS42L73_MICAPREPGAAVOL, CS42L73_MICBPREPGABVOL, 6, 1, 1), SOC_DOUBLE_R_SX_TLV("Input Path Digital Volume", CS42L73_IPADVOL, - CS42L73_IPBDVOL, 7, 0xffffffA0, 0xA0, ipd_tlv), + CS42L73_IPBDVOL, 0, 0xA0, 0x6C, ipd_tlv), SOC_DOUBLE_R_SX_TLV("HL Digital Playback Volume", - CS42L73_HLADVOL, CS42L73_HLBDVOL, 7, 0xffffffE5, - 0xE4, hl_tlv), + CS42L73_HLADVOL, CS42L73_HLBDVOL, + 0, 0x34, 0xE4, hl_tlv), SOC_SINGLE_TLV("ADC A Boost Volume", CS42L73_ADCIPC, 2, 0x01, 1, adc_boost_tlv), SOC_SINGLE_TLV("ADC B Boost Volume", - CS42L73_ADCIPC, 6, 0x01, 1, adc_boost_tlv), + CS42L73_ADCIPC, 6, 0x01, 1, adc_boost_tlv), - SOC_SINGLE_TLV("Speakerphone Digital Playback Volume", - CS42L73_SPKDVOL, 0, 0xE4, 1, hl_tlv), + SOC_SINGLE_SX_TLV("Speakerphone Digital Volume", + CS42L73_SPKDVOL, 0, 0x34, 0xE4, hl_tlv), - SOC_SINGLE_TLV("Ear Speaker Digital Playback Volume", - CS42L73_ESLDVOL, 0, 0xE4, 1, hl_tlv), + SOC_SINGLE_SX_TLV("Ear Speaker Digital Volume", + CS42L73_ESLDVOL, 0, 0x34, 0xE4, hl_tlv), SOC_DOUBLE_R("Headphone Analog Playback Switch", CS42L73_HPAAVOL, CS42L73_HPBAVOL, 7, 1, 1), @@ -599,17 +596,17 @@ static const struct snd_soc_dapm_widget cs42l73_dapm_widgets[] = { SND_SOC_DAPM_INPUT("MIC2"), SND_SOC_DAPM_SUPPLY("MIC2 Bias", CS42L73_PWRCTL2, 7, 1, NULL, 0), - SND_SOC_DAPM_AIF_OUT("XSPOUTL", "XSP Capture", 0, + SND_SOC_DAPM_AIF_OUT("XSPOUTL", NULL, 0, CS42L73_PWRCTL2, 1, 1), - SND_SOC_DAPM_AIF_OUT("XSPOUTR", "XSP Capture", 0, + SND_SOC_DAPM_AIF_OUT("XSPOUTR", NULL, 0, CS42L73_PWRCTL2, 1, 1), - SND_SOC_DAPM_AIF_OUT("ASPOUTL", "ASP Capture", 0, + SND_SOC_DAPM_AIF_OUT("ASPOUTL", NULL, 0, CS42L73_PWRCTL2, 3, 1), - SND_SOC_DAPM_AIF_OUT("ASPOUTR", "ASP Capture", 0, + SND_SOC_DAPM_AIF_OUT("ASPOUTR", NULL, 0, CS42L73_PWRCTL2, 3, 1), - SND_SOC_DAPM_AIF_OUT("VSPOUTL", "VSP Capture", 0, + SND_SOC_DAPM_AIF_OUT("VSPOUTL", NULL, 0, CS42L73_PWRCTL2, 4, 1), - SND_SOC_DAPM_AIF_OUT("VSPOUTR", "VSP Capture", 0, + SND_SOC_DAPM_AIF_OUT("VSPOUTR", NULL, 0, CS42L73_PWRCTL2, 4, 1), SND_SOC_DAPM_PGA("PGA Left", SND_SOC_NOPM, 0, 0, NULL, 0), @@ -638,21 +635,21 @@ static const struct snd_soc_dapm_widget cs42l73_dapm_widgets[] = { SND_SOC_DAPM_MIXER("VSPL Output Mixer", SND_SOC_NOPM, 0, 0, NULL, 0), SND_SOC_DAPM_MIXER("VSPR Output Mixer", SND_SOC_NOPM, 0, 0, NULL, 0), - SND_SOC_DAPM_AIF_IN("XSPINL", "XSP Playback", 0, + SND_SOC_DAPM_AIF_IN("XSPINL", NULL, 0, CS42L73_PWRCTL2, 0, 1), - SND_SOC_DAPM_AIF_IN("XSPINR", "XSP Playback", 0, + SND_SOC_DAPM_AIF_IN("XSPINR", NULL, 0, CS42L73_PWRCTL2, 0, 1), - SND_SOC_DAPM_AIF_IN("XSPINM", "XSP Playback", 0, + SND_SOC_DAPM_AIF_IN("XSPINM", NULL, 0, CS42L73_PWRCTL2, 0, 1), - SND_SOC_DAPM_AIF_IN("ASPINL", "ASP Playback", 0, + SND_SOC_DAPM_AIF_IN("ASPINL", NULL, 0, CS42L73_PWRCTL2, 2, 1), - SND_SOC_DAPM_AIF_IN("ASPINR", "ASP Playback", 0, + SND_SOC_DAPM_AIF_IN("ASPINR", NULL, 0, CS42L73_PWRCTL2, 2, 1), - SND_SOC_DAPM_AIF_IN("ASPINM", "ASP Playback", 0, + SND_SOC_DAPM_AIF_IN("ASPINM", NULL, 0, CS42L73_PWRCTL2, 2, 1), - SND_SOC_DAPM_AIF_IN("VSPIN", "VSP Playback", 0, + SND_SOC_DAPM_AIF_IN("VSPIN", NULL, 0, CS42L73_PWRCTL2, 4, 1), SND_SOC_DAPM_MIXER("HL Left Mixer", SND_SOC_NOPM, 0, 0, NULL, 0), @@ -776,6 +773,14 @@ static const struct snd_soc_dapm_route cs42l73_audio_map[] = { {"HL Left Mixer", NULL, "VSPIN"}, {"HL Right Mixer", NULL, "VSPIN"}, + {"ASPINL", NULL, "ASP Playback"}, + {"ASPINM", NULL, "ASP Playback"}, + {"ASPINR", NULL, "ASP Playback"}, + {"XSPINL", NULL, "XSP Playback"}, + {"XSPINM", NULL, "XSP Playback"}, + {"XSPINR", NULL, "XSP Playback"}, + {"VSPIN", NULL, "VSP Playback"}, + /* Capture Paths */ {"MIC1", NULL, "MIC1 Bias"}, {"PGA Left Mux", "Mic 1", "MIC1"}, @@ -822,6 +827,13 @@ static const struct snd_soc_dapm_route cs42l73_audio_map[] = { {"VSPOUTL", NULL, "VSPL Output Mixer"}, {"VSPOUTR", NULL, "VSPR Output Mixer"}, + + {"ASP Capture", NULL, "ASPOUTL"}, + {"ASP Capture", NULL, "ASPOUTR"}, + {"XSP Capture", NULL, "XSPOUTL"}, + {"XSP Capture", NULL, "XSPOUTR"}, + {"VSP Capture", NULL, "VSPOUTL"}, + {"VSP Capture", NULL, "VSPOUTR"}, }; struct cs42l73_mclk_div { @@ -1091,8 +1103,7 @@ static int cs42l73_pcm_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_codec *codec = dai->codec; struct cs42l73_private *priv = snd_soc_codec_get_drvdata(codec); int id = dai->id; int mclk_coeff; @@ -1429,25 +1440,7 @@ static struct i2c_driver cs42l73_i2c_driver = { }; -static int __init cs42l73_modinit(void) -{ - int ret; - ret = i2c_add_driver(&cs42l73_i2c_driver); - if (ret != 0) { - pr_err("Failed to register CS42L73 I2C driver: %d\n", ret); - return ret; - } - return 0; -} - -module_init(cs42l73_modinit); - -static void __exit cs42l73_exit(void) -{ - i2c_del_driver(&cs42l73_i2c_driver); -} - -module_exit(cs42l73_exit); +module_i2c_driver(cs42l73_i2c_driver); MODULE_DESCRIPTION("ASoC CS42L73 driver"); MODULE_AUTHOR("Georgi Vlaev, Nucleus Systems Ltd, <joe@nucleusys.com>"); diff --git a/sound/soc/codecs/da7210.c b/sound/soc/codecs/da7210.c index 7843711729b..af5db708051 100644 --- a/sound/soc/codecs/da7210.c +++ b/sound/soc/codecs/da7210.c @@ -17,6 +17,7 @@ #include <linux/delay.h> #include <linux/i2c.h> +#include <linux/spi/spi.h> #include <linux/regmap.h> #include <linux/slab.h> #include <linux/module.h> @@ -27,6 +28,7 @@ #include <sound/tlv.h> /* DA7210 register space */ +#define DA7210_PAGE_CONTROL 0x00 #define DA7210_CONTROL 0x01 #define DA7210_STATUS 0x02 #define DA7210_STARTUP1 0x03 @@ -146,6 +148,7 @@ #define DA7210_DAI_EN (1 << 7) /*PLL_DIV3 bit fields */ +#define DA7210_PLL_DIV_L_MASK (0xF << 0) #define DA7210_MCLK_RANGE_10_20_MHZ (1 << 4) #define DA7210_PLL_BYP (1 << 6) @@ -162,12 +165,16 @@ #define DA7210_PLL_FS_48000 (0xB << 0) #define DA7210_PLL_FS_88200 (0xE << 0) #define DA7210_PLL_FS_96000 (0xF << 0) +#define DA7210_MCLK_DET_EN (0x1 << 5) +#define DA7210_MCLK_SRM_EN (0x1 << 6) #define DA7210_PLL_EN (0x1 << 7) /* SOFTMUTE bit fields */ #define DA7210_RAMP_EN (1 << 6) /* CONTROL bit fields */ +#define DA7210_REG_EN (1 << 0) +#define DA7210_BIAS_EN (1 << 2) #define DA7210_NOISE_SUP_EN (1 << 3) /* IN_GAIN bit fields */ @@ -206,6 +213,47 @@ #define DA7210_OUT2_OUTMIX_L (1 << 6) #define DA7210_OUT2_EN (1 << 7) +struct pll_div { + int fref; + int fout; + u8 div1; + u8 div2; + u8 div3; + u8 mode; /* 0 = slave, 1 = master */ +}; + +/* PLL dividers table */ +static const struct pll_div da7210_pll_div[] = { + /* for MASTER mode, fs = 44.1Khz */ + { 12000000, 2822400, 0xE8, 0x6C, 0x2, 1}, /* MCLK=12Mhz */ + { 13000000, 2822400, 0xDF, 0x28, 0xC, 1}, /* MCLK=13Mhz */ + { 13500000, 2822400, 0xDB, 0x0A, 0xD, 1}, /* MCLK=13.5Mhz */ + { 14400000, 2822400, 0xD4, 0x5A, 0x2, 1}, /* MCLK=14.4Mhz */ + { 19200000, 2822400, 0xBB, 0x43, 0x9, 1}, /* MCLK=19.2Mhz */ + { 19680000, 2822400, 0xB9, 0x6D, 0xA, 1}, /* MCLK=19.68Mhz */ + { 19800000, 2822400, 0xB8, 0xFB, 0xB, 1}, /* MCLK=19.8Mhz */ + /* for MASTER mode, fs = 48Khz */ + { 12000000, 3072000, 0xF3, 0x12, 0x7, 1}, /* MCLK=12Mhz */ + { 13000000, 3072000, 0xE8, 0xFD, 0x5, 1}, /* MCLK=13Mhz */ + { 13500000, 3072000, 0xE4, 0x82, 0x3, 1}, /* MCLK=13.5Mhz */ + { 14400000, 3072000, 0xDD, 0x3A, 0x0, 1}, /* MCLK=14.4Mhz */ + { 19200000, 3072000, 0xC1, 0xEB, 0x8, 1}, /* MCLK=19.2Mhz */ + { 19680000, 3072000, 0xBF, 0xEC, 0x0, 1}, /* MCLK=19.68Mhz */ + { 19800000, 3072000, 0xBF, 0x70, 0x0, 1}, /* MCLK=19.8Mhz */ + /* for SLAVE mode with SRM */ + { 12000000, 2822400, 0xED, 0xBF, 0x5, 0}, /* MCLK=12Mhz */ + { 13000000, 2822400, 0xE4, 0x13, 0x0, 0}, /* MCLK=13Mhz */ + { 13500000, 2822400, 0xDF, 0xC6, 0x8, 0}, /* MCLK=13.5Mhz */ + { 14400000, 2822400, 0xD8, 0xCA, 0x1, 0}, /* MCLK=14.4Mhz */ + { 19200000, 2822400, 0xBE, 0x97, 0x9, 0}, /* MCLK=19.2Mhz */ + { 19680000, 2822400, 0xBC, 0xAC, 0xD, 0}, /* MCLK=19.68Mhz */ + { 19800000, 2822400, 0xBC, 0x35, 0xE, 0}, /* MCLK=19.8Mhz */ +}; + +enum clk_src { + DA7210_CLKSRC_MCLK +}; + #define DA7210_VERSION "0.0.1" /* @@ -628,9 +676,12 @@ static const struct snd_soc_dapm_route da7210_audio_map[] = { /* Codec private data */ struct da7210_priv { struct regmap *regmap; + unsigned int mclk_rate; + int master; }; static struct reg_default da7210_reg_defaults[] = { + { 0x00, 0x00 }, { 0x01, 0x11 }, { 0x03, 0x00 }, { 0x04, 0x00 }, @@ -713,10 +764,10 @@ static int da7210_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_codec *codec = dai->codec; + struct da7210_priv *da7210 = snd_soc_codec_get_drvdata(codec); u32 dai_cfg1; - u32 fs, bypass; + u32 fs, sysclk; /* set DAI source to Left and Right ADC */ snd_soc_write(codec, DA7210_DAI_SRC_SEL, @@ -749,43 +800,43 @@ static int da7210_hw_params(struct snd_pcm_substream *substream, switch (params_rate(params)) { case 8000: fs = DA7210_PLL_FS_8000; - bypass = DA7210_PLL_BYP; + sysclk = 3072000; break; case 11025: fs = DA7210_PLL_FS_11025; - bypass = 0; + sysclk = 2822400; break; case 12000: fs = DA7210_PLL_FS_12000; - bypass = DA7210_PLL_BYP; + sysclk = 3072000; break; case 16000: fs = DA7210_PLL_FS_16000; - bypass = DA7210_PLL_BYP; + sysclk = 3072000; break; case 22050: fs = DA7210_PLL_FS_22050; - bypass = 0; + sysclk = 2822400; break; case 32000: fs = DA7210_PLL_FS_32000; - bypass = DA7210_PLL_BYP; + sysclk = 3072000; break; case 44100: fs = DA7210_PLL_FS_44100; - bypass = 0; + sysclk = 2822400; break; case 48000: fs = DA7210_PLL_FS_48000; - bypass = DA7210_PLL_BYP; + sysclk = 3072000; break; case 88200: fs = DA7210_PLL_FS_88200; - bypass = 0; + sysclk = 2822400; break; case 96000: fs = DA7210_PLL_FS_96000; - bypass = DA7210_PLL_BYP; + sysclk = 3072000; break; default: return -EINVAL; @@ -795,8 +846,26 @@ static int da7210_hw_params(struct snd_pcm_substream *substream, snd_soc_update_bits(codec, DA7210_STARTUP1, DA7210_SC_MST_EN, 0); snd_soc_update_bits(codec, DA7210_PLL, DA7210_PLL_FS_MASK, fs); - snd_soc_update_bits(codec, DA7210_PLL_DIV3, DA7210_PLL_BYP, bypass); + if (da7210->mclk_rate && (da7210->mclk_rate != sysclk)) { + /* PLL mode, disable PLL bypass */ + snd_soc_update_bits(codec, DA7210_PLL_DIV3, DA7210_PLL_BYP, 0); + + if (!da7210->master) { + /* PLL slave mode, also enable SRM */ + snd_soc_update_bits(codec, DA7210_PLL, + (DA7210_MCLK_SRM_EN | + DA7210_MCLK_DET_EN), + (DA7210_MCLK_SRM_EN | + DA7210_MCLK_DET_EN)); + } + } else { + /* PLL bypass mode, enable PLL bypass and Auto Detection */ + snd_soc_update_bits(codec, DA7210_PLL, DA7210_MCLK_DET_EN, + DA7210_MCLK_DET_EN); + snd_soc_update_bits(codec, DA7210_PLL_DIV3, DA7210_PLL_BYP, + DA7210_PLL_BYP); + } /* Enable active mode */ snd_soc_update_bits(codec, DA7210_STARTUP1, DA7210_SC_MST_EN, DA7210_SC_MST_EN); @@ -810,17 +879,24 @@ static int da7210_hw_params(struct snd_pcm_substream *substream, static int da7210_set_dai_fmt(struct snd_soc_dai *codec_dai, u32 fmt) { struct snd_soc_codec *codec = codec_dai->codec; + struct da7210_priv *da7210 = snd_soc_codec_get_drvdata(codec); u32 dai_cfg1; u32 dai_cfg3; dai_cfg1 = 0x7f & snd_soc_read(codec, DA7210_DAI_CFG1); dai_cfg3 = 0xfc & snd_soc_read(codec, DA7210_DAI_CFG3); + if ((snd_soc_read(codec, DA7210_PLL) & DA7210_PLL_EN) && + (!(snd_soc_read(codec, DA7210_PLL_DIV3) & DA7210_PLL_BYP))) + return -EINVAL; + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { case SND_SOC_DAIFMT_CBM_CFM: + da7210->master = 1; dai_cfg1 |= DA7210_DAI_MODE_MASTER; break; case SND_SOC_DAIFMT_CBS_CFS: + da7210->master = 0; dai_cfg1 |= DA7210_DAI_MODE_SLAVE; break; default: @@ -872,10 +948,101 @@ static int da7210_mute(struct snd_soc_dai *dai, int mute) #define DA7210_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) +static int da7210_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 da7210_priv *da7210 = snd_soc_codec_get_drvdata(codec); + + switch (clk_id) { + case DA7210_CLKSRC_MCLK: + switch (freq) { + case 12000000: + case 13000000: + case 13500000: + case 14400000: + case 19200000: + case 19680000: + case 19800000: + da7210->mclk_rate = freq; + return 0; + default: + dev_err(codec_dai->dev, "Unsupported MCLK value %d\n", + freq); + return -EINVAL; + } + break; + default: + dev_err(codec_dai->dev, "Unknown clock source %d\n", clk_id); + return -EINVAL; + } +} + +/** + * da7210_set_dai_pll :Configure the codec PLL + * @param codec_dai : pointer to codec DAI + * @param pll_id : da7210 has only one pll, so pll_id is always zero + * @param fref : MCLK frequency, should be < 20MHz + * @param fout : FsDM value, Refer page 44 & 45 of datasheet + * @return int : Zero for success, negative error code for error + * + * Note: Supported PLL input frequencies are 12MHz, 13MHz, 13.5MHz, 14.4MHz, + * 19.2MHz, 19.6MHz and 19.8MHz + */ +static int da7210_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id, + int source, unsigned int fref, unsigned int fout) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct da7210_priv *da7210 = snd_soc_codec_get_drvdata(codec); + + u8 pll_div1, pll_div2, pll_div3, cnt; + + /* In slave mode, there is only one set of divisors */ + if (!da7210->master) + fout = 2822400; + + /* Search pll div array for correct divisors */ + for (cnt = 0; cnt < ARRAY_SIZE(da7210_pll_div); cnt++) { + /* check fref, mode and fout */ + if ((fref == da7210_pll_div[cnt].fref) && + (da7210->master == da7210_pll_div[cnt].mode) && + (fout == da7210_pll_div[cnt].fout)) { + /* all match, pick up divisors */ + pll_div1 = da7210_pll_div[cnt].div1; + pll_div2 = da7210_pll_div[cnt].div2; + pll_div3 = da7210_pll_div[cnt].div3; + break; + } + } + if (cnt >= ARRAY_SIZE(da7210_pll_div)) + goto err; + + /* Disable active mode */ + snd_soc_update_bits(codec, DA7210_STARTUP1, DA7210_SC_MST_EN, 0); + /* Write PLL dividers */ + snd_soc_write(codec, DA7210_PLL_DIV1, pll_div1); + snd_soc_write(codec, DA7210_PLL_DIV2, pll_div2); + snd_soc_update_bits(codec, DA7210_PLL_DIV3, + DA7210_PLL_DIV_L_MASK, pll_div3); + + /* Enable PLL */ + snd_soc_update_bits(codec, DA7210_PLL, DA7210_PLL_EN, DA7210_PLL_EN); + + /* Enable active mode */ + snd_soc_update_bits(codec, DA7210_STARTUP1, DA7210_SC_MST_EN, + DA7210_SC_MST_EN); + return 0; +err: + dev_err(codec_dai->dev, "Unsupported PLL input frequency %d\n", fref); + return -EINVAL; +} + /* DAI operations */ static const struct snd_soc_dai_ops da7210_dai_ops = { .hw_params = da7210_hw_params, .set_fmt = da7210_set_dai_fmt, + .set_sysclk = da7210_set_dai_sysclk, + .set_pll = da7210_set_dai_pll, .digital_mute = da7210_mute, }; @@ -915,24 +1082,11 @@ static int da7210_probe(struct snd_soc_codec *codec) return ret; } - /* FIXME - * - * This driver use fixed value here - * And below settings expects MCLK = 12.288MHz - * - * When you select different MCLK, please check... - * DA7210_PLL_DIV1 val - * DA7210_PLL_DIV2 val - * DA7210_PLL_DIV3 val - * DA7210_PLL_DIV3 :: DA7210_MCLK_RANGExxx - */ + da7210->mclk_rate = 0; /* This will be set from set_sysclk() */ + da7210->master = 0; /* This will be set from set_fmt() */ - /* - * make sure that DA7210 use bypass mode before start up - */ - snd_soc_write(codec, DA7210_STARTUP1, 0); - snd_soc_write(codec, DA7210_PLL_DIV3, - DA7210_MCLK_RANGE_10_20_MHZ | DA7210_PLL_BYP); + /* Enable internal regulator & bias current */ + snd_soc_write(codec, DA7210_CONTROL, DA7210_REG_EN | DA7210_BIAS_EN); /* * ADC settings @@ -1007,34 +1161,13 @@ static int da7210_probe(struct snd_soc_codec *codec) /* Enable Aux2 */ snd_soc_write(codec, DA7210_AUX2, DA7210_AUX2_EN); + /* Set PLL Master clock range 10-20 MHz, enable PLL bypass */ + snd_soc_write(codec, DA7210_PLL_DIV3, DA7210_MCLK_RANGE_10_20_MHZ | + DA7210_PLL_BYP); + /* Diable PLL and bypass it */ snd_soc_write(codec, DA7210_PLL, DA7210_PLL_FS_48000); - /* - * If 48kHz sound came, it use bypass mode, - * and when it is 44.1kHz, it use PLL. - * - * This time, this driver sets PLL always ON - * and controls bypass/PLL mode by switching - * DA7210_PLL_DIV3 :: DA7210_PLL_BYP bit. - * see da7210_hw_params - */ - snd_soc_write(codec, DA7210_PLL_DIV1, 0xE5); /* MCLK = 12.288MHz */ - snd_soc_write(codec, DA7210_PLL_DIV2, 0x99); - snd_soc_write(codec, DA7210_PLL_DIV3, 0x0A | - DA7210_MCLK_RANGE_10_20_MHZ | DA7210_PLL_BYP); - snd_soc_update_bits(codec, DA7210_PLL, DA7210_PLL_EN, DA7210_PLL_EN); - - /* As suggested by Dialog */ - /* unlock */ - regmap_write(da7210->regmap, DA7210_A_HID_UNLOCK, 0x8B); - regmap_write(da7210->regmap, DA7210_A_TEST_UNLOCK, 0xB4); - regmap_write(da7210->regmap, DA7210_A_PLL1, 0x01); - regmap_write(da7210->regmap, DA7210_A_CP_MODE, 0x7C); - /* re-lock */ - regmap_write(da7210->regmap, DA7210_A_HID_UNLOCK, 0x00); - regmap_write(da7210->regmap, DA7210_A_TEST_UNLOCK, 0x00); - /* Activate all enabled subsystem */ snd_soc_write(codec, DA7210_STARTUP1, DA7210_SC_MST_EN); @@ -1055,7 +1188,26 @@ static struct snd_soc_codec_driver soc_codec_dev_da7210 = { .num_dapm_routes = ARRAY_SIZE(da7210_audio_map), }; -static struct regmap_config da7210_regmap = { +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + +static struct reg_default da7210_regmap_i2c_patch[] = { + + /* System controller master disable */ + { DA7210_STARTUP1, 0x00 }, + /* Set PLL Master clock range 10-20 MHz */ + { DA7210_PLL_DIV3, DA7210_MCLK_RANGE_10_20_MHZ }, + + /* to unlock */ + { DA7210_A_HID_UNLOCK, 0x8B}, + { DA7210_A_TEST_UNLOCK, 0xB4}, + { DA7210_A_PLL1, 0x01}, + { DA7210_A_CP_MODE, 0x7C}, + /* to re-lock */ + { DA7210_A_HID_UNLOCK, 0x00}, + { DA7210_A_TEST_UNLOCK, 0x00}, +}; + +static const struct regmap_config da7210_regmap_config_i2c = { .reg_bits = 8, .val_bits = 8, @@ -1066,7 +1218,6 @@ static struct regmap_config da7210_regmap = { .cache_type = REGCACHE_RBTREE, }; -#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) static int __devinit da7210_i2c_probe(struct i2c_client *i2c, const struct i2c_device_id *id) { @@ -1080,13 +1231,18 @@ static int __devinit da7210_i2c_probe(struct i2c_client *i2c, i2c_set_clientdata(i2c, da7210); - da7210->regmap = regmap_init_i2c(i2c, &da7210_regmap); + da7210->regmap = regmap_init_i2c(i2c, &da7210_regmap_config_i2c); if (IS_ERR(da7210->regmap)) { ret = PTR_ERR(da7210->regmap); dev_err(&i2c->dev, "regmap_init() failed: %d\n", ret); return ret; } + ret = regmap_register_patch(da7210->regmap, da7210_regmap_i2c_patch, + ARRAY_SIZE(da7210_regmap_i2c_patch)); + if (ret != 0) + dev_warn(&i2c->dev, "Failed to apply regmap patch: %d\n", ret); + ret = snd_soc_register_codec(&i2c->dev, &soc_codec_dev_da7210, &da7210_dai, 1); if (ret < 0) { @@ -1119,7 +1275,7 @@ MODULE_DEVICE_TABLE(i2c, da7210_i2c_id); /* I2C codec control layer */ static struct i2c_driver da7210_i2c_driver = { .driver = { - .name = "da7210-codec", + .name = "da7210", .owner = THIS_MODULE, }, .probe = da7210_i2c_probe, @@ -1128,12 +1284,112 @@ static struct i2c_driver da7210_i2c_driver = { }; #endif +#if defined(CONFIG_SPI_MASTER) + +static struct reg_default da7210_regmap_spi_patch[] = { + /* Dummy read to give two pulses over nCS for SPI */ + { DA7210_AUX2, 0x00 }, + { DA7210_AUX2, 0x00 }, + + /* System controller master disable */ + { DA7210_STARTUP1, 0x00 }, + /* Set PLL Master clock range 10-20 MHz */ + { DA7210_PLL_DIV3, DA7210_MCLK_RANGE_10_20_MHZ }, + + /* to set PAGE1 of SPI register space */ + { DA7210_PAGE_CONTROL, 0x80 }, + /* to unlock */ + { DA7210_A_HID_UNLOCK, 0x8B}, + { DA7210_A_TEST_UNLOCK, 0xB4}, + { DA7210_A_PLL1, 0x01}, + { DA7210_A_CP_MODE, 0x7C}, + /* to re-lock */ + { DA7210_A_HID_UNLOCK, 0x00}, + { DA7210_A_TEST_UNLOCK, 0x00}, + /* to set back PAGE0 of SPI register space */ + { DA7210_PAGE_CONTROL, 0x00 }, +}; + +static const struct regmap_config da7210_regmap_config_spi = { + .reg_bits = 8, + .val_bits = 8, + .read_flag_mask = 0x01, + .write_flag_mask = 0x00, + + .reg_defaults = da7210_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(da7210_reg_defaults), + .volatile_reg = da7210_volatile_register, + .readable_reg = da7210_readable_register, + .cache_type = REGCACHE_RBTREE, +}; + +static int __devinit da7210_spi_probe(struct spi_device *spi) +{ + struct da7210_priv *da7210; + int ret; + + da7210 = devm_kzalloc(&spi->dev, sizeof(struct da7210_priv), + GFP_KERNEL); + if (!da7210) + return -ENOMEM; + + spi_set_drvdata(spi, da7210); + da7210->regmap = devm_regmap_init_spi(spi, &da7210_regmap_config_spi); + if (IS_ERR(da7210->regmap)) { + ret = PTR_ERR(da7210->regmap); + dev_err(&spi->dev, "Failed to register regmap: %d\n", ret); + return ret; + } + + ret = regmap_register_patch(da7210->regmap, da7210_regmap_spi_patch, + ARRAY_SIZE(da7210_regmap_spi_patch)); + if (ret != 0) + dev_warn(&spi->dev, "Failed to apply regmap patch: %d\n", ret); + + ret = snd_soc_register_codec(&spi->dev, + &soc_codec_dev_da7210, &da7210_dai, 1); + if (ret < 0) + goto err_regmap; + + return ret; + +err_regmap: + regmap_exit(da7210->regmap); + + return ret; +} + +static int __devexit da7210_spi_remove(struct spi_device *spi) +{ + struct da7210_priv *da7210 = spi_get_drvdata(spi); + snd_soc_unregister_codec(&spi->dev); + regmap_exit(da7210->regmap); + return 0; +} + +static struct spi_driver da7210_spi_driver = { + .driver = { + .name = "da7210", + .owner = THIS_MODULE, + }, + .probe = da7210_spi_probe, + .remove = __devexit_p(da7210_spi_remove) +}; +#endif + static int __init da7210_modinit(void) { int ret = 0; #if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) ret = i2c_add_driver(&da7210_i2c_driver); #endif +#if defined(CONFIG_SPI_MASTER) + ret = spi_register_driver(&da7210_spi_driver); + if (ret) { + printk(KERN_ERR "Failed to register da7210 SPI driver: %d\n", + ret); + } +#endif return ret; } module_init(da7210_modinit); @@ -1143,6 +1399,9 @@ static void __exit da7210_exit(void) #if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) i2c_del_driver(&da7210_i2c_driver); #endif +#if defined(CONFIG_SPI_MASTER) + spi_unregister_driver(&da7210_spi_driver); +#endif } module_exit(da7210_exit); diff --git a/sound/soc/codecs/jz4740.c b/sound/soc/codecs/jz4740.c index 4624e752a18..85d9cabe6d5 100644 --- a/sound/soc/codecs/jz4740.c +++ b/sound/soc/codecs/jz4740.c @@ -164,8 +164,7 @@ static int jz4740_codec_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { uint32_t val; - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_codec *codec =rtd->codec; + struct snd_soc_codec *codec = dai->codec; switch (params_rate(params)) { case 8000: diff --git a/sound/soc/codecs/lm49453.c b/sound/soc/codecs/lm49453.c new file mode 100644 index 00000000000..802b9f176b1 --- /dev/null +++ b/sound/soc/codecs/lm49453.c @@ -0,0 +1,1550 @@ +/* + * lm49453.c - LM49453 ALSA Soc Audio driver + * + * Copyright (c) 2012 Texas Instruments, Inc + * + * 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. + * + * Initially based on sound/soc/codecs/wm8350.c + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/version.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/i2c.h> +#include <linux/regmap.h> +#include <linux/slab.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/tlv.h> +#include <sound/jack.h> +#include <sound/initval.h> +#include <asm/div64.h> +#include "lm49453.h" + +static struct reg_default lm49453_reg_defs[] = { + { 0, 0x00 }, + { 1, 0x00 }, + { 2, 0x00 }, + { 3, 0x00 }, + { 4, 0x00 }, + { 5, 0x00 }, + { 6, 0x00 }, + { 7, 0x00 }, + { 8, 0x00 }, + { 9, 0x00 }, + { 10, 0x00 }, + { 11, 0x00 }, + { 12, 0x00 }, + { 13, 0x00 }, + { 14, 0x00 }, + { 15, 0x00 }, + { 16, 0x00 }, + { 17, 0x00 }, + { 18, 0x00 }, + { 19, 0x00 }, + { 20, 0x00 }, + { 21, 0x00 }, + { 22, 0x00 }, + { 23, 0x00 }, + { 32, 0x00 }, + { 33, 0x00 }, + { 35, 0x00 }, + { 36, 0x00 }, + { 37, 0x00 }, + { 46, 0x00 }, + { 48, 0x00 }, + { 49, 0x00 }, + { 51, 0x00 }, + { 56, 0x00 }, + { 58, 0x00 }, + { 59, 0x00 }, + { 60, 0x00 }, + { 61, 0x00 }, + { 62, 0x00 }, + { 63, 0x00 }, + { 64, 0x00 }, + { 65, 0x00 }, + { 66, 0x00 }, + { 67, 0x00 }, + { 68, 0x00 }, + { 69, 0x00 }, + { 70, 0x00 }, + { 71, 0x00 }, + { 72, 0x00 }, + { 73, 0x00 }, + { 74, 0x00 }, + { 75, 0x00 }, + { 76, 0x00 }, + { 77, 0x00 }, + { 78, 0x00 }, + { 79, 0x00 }, + { 80, 0x00 }, + { 81, 0x00 }, + { 82, 0x00 }, + { 83, 0x00 }, + { 85, 0x00 }, + { 85, 0x00 }, + { 86, 0x00 }, + { 87, 0x00 }, + { 88, 0x00 }, + { 89, 0x00 }, + { 90, 0x00 }, + { 91, 0x00 }, + { 92, 0x00 }, + { 93, 0x00 }, + { 94, 0x00 }, + { 95, 0x00 }, + { 96, 0x01 }, + { 97, 0x00 }, + { 98, 0x00 }, + { 99, 0x00 }, + { 100, 0x00 }, + { 101, 0x00 }, + { 102, 0x00 }, + { 103, 0x01 }, + { 105, 0x01 }, + { 106, 0x00 }, + { 107, 0x01 }, + { 107, 0x00 }, + { 108, 0x00 }, + { 109, 0x00 }, + { 110, 0x00 }, + { 111, 0x02 }, + { 112, 0x02 }, + { 113, 0x00 }, + { 121, 0x80 }, + { 122, 0xBB }, + { 123, 0x80 }, + { 124, 0xBB }, + { 128, 0x00 }, + { 130, 0x00 }, + { 131, 0x00 }, + { 132, 0x00 }, + { 133, 0x0A }, + { 134, 0x0A }, + { 135, 0x0A }, + { 136, 0x0F }, + { 137, 0x00 }, + { 138, 0x73 }, + { 139, 0x33 }, + { 140, 0x73 }, + { 141, 0x33 }, + { 142, 0x73 }, + { 143, 0x33 }, + { 144, 0x73 }, + { 145, 0x33 }, + { 146, 0x73 }, + { 147, 0x33 }, + { 148, 0x73 }, + { 149, 0x33 }, + { 150, 0x73 }, + { 151, 0x33 }, + { 152, 0x00 }, + { 153, 0x00 }, + { 154, 0x00 }, + { 155, 0x00 }, + { 176, 0x00 }, + { 177, 0x00 }, + { 178, 0x00 }, + { 179, 0x00 }, + { 180, 0x00 }, + { 181, 0x00 }, + { 182, 0x00 }, + { 183, 0x00 }, + { 184, 0x00 }, + { 185, 0x00 }, + { 186, 0x00 }, + { 189, 0x00 }, + { 188, 0x00 }, + { 194, 0x00 }, + { 195, 0x00 }, + { 196, 0x00 }, + { 197, 0x00 }, + { 200, 0x00 }, + { 201, 0x00 }, + { 202, 0x00 }, + { 203, 0x00 }, + { 204, 0x00 }, + { 205, 0x00 }, + { 208, 0x00 }, + { 209, 0x00 }, + { 210, 0x00 }, + { 211, 0x00 }, + { 213, 0x00 }, + { 214, 0x00 }, + { 215, 0x00 }, + { 216, 0x00 }, + { 217, 0x00 }, + { 218, 0x00 }, + { 219, 0x00 }, + { 221, 0x00 }, + { 222, 0x00 }, + { 224, 0x00 }, + { 225, 0x00 }, + { 226, 0x00 }, + { 227, 0x00 }, + { 228, 0x00 }, + { 229, 0x00 }, + { 230, 0x13 }, + { 231, 0x00 }, + { 232, 0x80 }, + { 233, 0x0C }, + { 234, 0xDD }, + { 235, 0x00 }, + { 236, 0x04 }, + { 237, 0x00 }, + { 238, 0x00 }, + { 239, 0x00 }, + { 240, 0x00 }, + { 241, 0x00 }, + { 242, 0x00 }, + { 243, 0x00 }, + { 244, 0x00 }, + { 245, 0x00 }, + { 248, 0x00 }, + { 249, 0x00 }, + { 254, 0x00 }, + { 255, 0x00 }, +}; + +/* codec private data */ +struct lm49453_priv { + struct regmap *regmap; + int fs_rate; +}; + +/* capture path controls */ + +static const char *lm49453_mic2mode_text[] = {"Single Ended", "Differential"}; + +static const SOC_ENUM_SINGLE_DECL(lm49453_mic2mode_enum, LM49453_P0_MICR_REG, 5, + lm49453_mic2mode_text); + +static const char *lm49453_dmic_cfg_text[] = {"DMICDAT1", "DMICDAT2"}; + +static const SOC_ENUM_SINGLE_DECL(lm49453_dmic12_cfg_enum, + LM49453_P0_DIGITAL_MIC1_CONFIG_REG, + 7, lm49453_dmic_cfg_text); + +static const SOC_ENUM_SINGLE_DECL(lm49453_dmic34_cfg_enum, + LM49453_P0_DIGITAL_MIC2_CONFIG_REG, + 7, lm49453_dmic_cfg_text); + +/* MUX Controls */ +static const char *lm49453_adcl_mux_text[] = { "MIC1", "Aux_L" }; + +static const char *lm49453_adcr_mux_text[] = { "MIC2", "Aux_R" }; + +static const struct soc_enum lm49453_adcl_enum = + SOC_ENUM_SINGLE(LM49453_P0_ANALOG_MIXER_ADC_REG, 0, + ARRAY_SIZE(lm49453_adcl_mux_text), + lm49453_adcl_mux_text); + +static const struct soc_enum lm49453_adcr_enum = + SOC_ENUM_SINGLE(LM49453_P0_ANALOG_MIXER_ADC_REG, 1, + ARRAY_SIZE(lm49453_adcr_mux_text), + lm49453_adcr_mux_text); + +static const struct snd_kcontrol_new lm49453_adcl_mux_control = + SOC_DAPM_ENUM("ADC Left Mux", lm49453_adcl_enum); + +static const struct snd_kcontrol_new lm49453_adcr_mux_control = + SOC_DAPM_ENUM("ADC Right Mux", lm49453_adcr_enum); + +static const struct snd_kcontrol_new lm49453_headset_left_mixer[] = { +SOC_DAPM_SINGLE("Port1_1 Switch", LM49453_P0_DACHPL1_REG, 0, 1, 0), +SOC_DAPM_SINGLE("Port1_2 Switch", LM49453_P0_DACHPL1_REG, 1, 1, 0), +SOC_DAPM_SINGLE("Port1_3 Switch", LM49453_P0_DACHPL1_REG, 2, 1, 0), +SOC_DAPM_SINGLE("Port1_4 Switch", LM49453_P0_DACHPL1_REG, 3, 1, 0), +SOC_DAPM_SINGLE("Port1_5 Switch", LM49453_P0_DACHPL1_REG, 4, 1, 0), +SOC_DAPM_SINGLE("Port1_6 Switch", LM49453_P0_DACHPL1_REG, 5, 1, 0), +SOC_DAPM_SINGLE("Port1_7 Switch", LM49453_P0_DACHPL1_REG, 6, 1, 0), +SOC_DAPM_SINGLE("Port1_8 Switch", LM49453_P0_DACHPL1_REG, 7, 1, 0), +SOC_DAPM_SINGLE("DMIC1L Switch", LM49453_P0_DACHPL2_REG, 0, 1, 0), +SOC_DAPM_SINGLE("DMIC1R Switch", LM49453_P0_DACHPL2_REG, 1, 1, 0), +SOC_DAPM_SINGLE("DMIC2L Switch", LM49453_P0_DACHPL2_REG, 2, 1, 0), +SOC_DAPM_SINGLE("DMIC2R Switch", LM49453_P0_DACHPL2_REG, 3, 1, 0), +SOC_DAPM_SINGLE("ADCL Switch", LM49453_P0_DACHPL2_REG, 4, 1, 0), +SOC_DAPM_SINGLE("ADCR Switch", LM49453_P0_DACHPL2_REG, 5, 1, 0), +SOC_DAPM_SINGLE("Port2_1 Switch", LM49453_P0_DACHPL2_REG, 6, 1, 0), +SOC_DAPM_SINGLE("Port2_2 Switch", LM49453_P0_DACHPL2_REG, 7, 1, 0), +SOC_DAPM_SINGLE("Sidetone Switch", LM49453_P0_STN_SEL_REG, 0, 0, 0), +}; + +static const struct snd_kcontrol_new lm49453_headset_right_mixer[] = { +SOC_DAPM_SINGLE("Port1_1 Switch", LM49453_P0_DACHPR1_REG, 0, 1, 0), +SOC_DAPM_SINGLE("Port1_2 Switch", LM49453_P0_DACHPR1_REG, 1, 1, 0), +SOC_DAPM_SINGLE("Port1_3 Switch", LM49453_P0_DACHPR1_REG, 2, 1, 0), +SOC_DAPM_SINGLE("Port1_4 Switch", LM49453_P0_DACHPR1_REG, 3, 1, 0), +SOC_DAPM_SINGLE("Port1_5 Switch", LM49453_P0_DACHPR1_REG, 4, 1, 0), +SOC_DAPM_SINGLE("Port1_6 Switch", LM49453_P0_DACHPR1_REG, 5, 1, 0), +SOC_DAPM_SINGLE("Port1_7 Switch", LM49453_P0_DACHPR1_REG, 6, 1, 0), +SOC_DAPM_SINGLE("Port1_8 Switch", LM49453_P0_DACHPR1_REG, 7, 1, 0), +SOC_DAPM_SINGLE("DMIC1L Switch", LM49453_P0_DACHPR2_REG, 0, 1, 0), +SOC_DAPM_SINGLE("DMIC1R Switch", LM49453_P0_DACHPR2_REG, 1, 1, 0), +SOC_DAPM_SINGLE("DMIC2L Switch", LM49453_P0_DACHPR2_REG, 2, 1, 0), +SOC_DAPM_SINGLE("DMIC2R Switch", LM49453_P0_DACHPR2_REG, 3, 1, 0), +SOC_DAPM_SINGLE("ADCL Switch", LM49453_P0_DACHPR2_REG, 4, 1, 0), +SOC_DAPM_SINGLE("ADCR Switch", LM49453_P0_DACHPR2_REG, 5, 1, 0), +SOC_DAPM_SINGLE("Port2_1 Switch", LM49453_P0_DACHPR2_REG, 6, 1, 0), +SOC_DAPM_SINGLE("Port2_2 Switch", LM49453_P0_DACHPR2_REG, 7, 1, 0), +SOC_DAPM_SINGLE("Sidetone Switch", LM49453_P0_STN_SEL_REG, 1, 0, 0), +}; + +static const struct snd_kcontrol_new lm49453_speaker_left_mixer[] = { +SOC_DAPM_SINGLE("Port1_1 Switch", LM49453_P0_DACLSL1_REG, 0, 1, 0), +SOC_DAPM_SINGLE("Port1_2 Switch", LM49453_P0_DACLSL1_REG, 1, 1, 0), +SOC_DAPM_SINGLE("Port1_3 Switch", LM49453_P0_DACLSL1_REG, 2, 1, 0), +SOC_DAPM_SINGLE("Port1_4 Switch", LM49453_P0_DACLSL1_REG, 3, 1, 0), +SOC_DAPM_SINGLE("Port1_5 Switch", LM49453_P0_DACLSL1_REG, 4, 1, 0), +SOC_DAPM_SINGLE("Port1_6 Switch", LM49453_P0_DACLSL1_REG, 5, 1, 0), +SOC_DAPM_SINGLE("Port1_7 Switch", LM49453_P0_DACLSL1_REG, 6, 1, 0), +SOC_DAPM_SINGLE("Port1_8 Switch", LM49453_P0_DACLSL1_REG, 7, 1, 0), +SOC_DAPM_SINGLE("DMIC1L Switch", LM49453_P0_DACLSL2_REG, 0, 1, 0), +SOC_DAPM_SINGLE("DMIC1R Switch", LM49453_P0_DACLSL2_REG, 1, 1, 0), +SOC_DAPM_SINGLE("DMIC2L Switch", LM49453_P0_DACLSL2_REG, 2, 1, 0), +SOC_DAPM_SINGLE("DMIC2R Switch", LM49453_P0_DACLSL2_REG, 3, 1, 0), +SOC_DAPM_SINGLE("ADCL Switch", LM49453_P0_DACLSL2_REG, 4, 1, 0), +SOC_DAPM_SINGLE("ADCR Switch", LM49453_P0_DACLSL2_REG, 5, 1, 0), +SOC_DAPM_SINGLE("Port2_1 Switch", LM49453_P0_DACLSL2_REG, 6, 1, 0), +SOC_DAPM_SINGLE("Port2_2 Switch", LM49453_P0_DACLSL2_REG, 7, 1, 0), +SOC_DAPM_SINGLE("Sidetone Switch", LM49453_P0_STN_SEL_REG, 2, 0, 0), +}; + +static const struct snd_kcontrol_new lm49453_speaker_right_mixer[] = { +SOC_DAPM_SINGLE("Port1_1 Switch", LM49453_P0_DACLSR1_REG, 0, 1, 0), +SOC_DAPM_SINGLE("Port1_2 Switch", LM49453_P0_DACLSR1_REG, 1, 1, 0), +SOC_DAPM_SINGLE("Port1_3 Switch", LM49453_P0_DACLSR1_REG, 2, 1, 0), +SOC_DAPM_SINGLE("Port1_4 Switch", LM49453_P0_DACLSR1_REG, 3, 1, 0), +SOC_DAPM_SINGLE("Port1_5 Switch", LM49453_P0_DACLSR1_REG, 4, 1, 0), +SOC_DAPM_SINGLE("Port1_6 Switch", LM49453_P0_DACLSR1_REG, 5, 1, 0), +SOC_DAPM_SINGLE("Port1_7 Switch", LM49453_P0_DACLSR1_REG, 6, 1, 0), +SOC_DAPM_SINGLE("Port1_8 Switch", LM49453_P0_DACLSR1_REG, 7, 1, 0), +SOC_DAPM_SINGLE("DMIC1L Switch", LM49453_P0_DACLSR2_REG, 0, 1, 0), +SOC_DAPM_SINGLE("DMIC1R Switch", LM49453_P0_DACLSR2_REG, 1, 1, 0), +SOC_DAPM_SINGLE("DMIC2L Switch", LM49453_P0_DACLSR2_REG, 2, 1, 0), +SOC_DAPM_SINGLE("DMIC2R Switch", LM49453_P0_DACLSR2_REG, 3, 1, 0), +SOC_DAPM_SINGLE("ADCL Switch", LM49453_P0_DACLSR2_REG, 4, 1, 0), +SOC_DAPM_SINGLE("ADCR Switch", LM49453_P0_DACLSR2_REG, 5, 1, 0), +SOC_DAPM_SINGLE("Port2_1 Switch", LM49453_P0_DACLSR2_REG, 6, 1, 0), +SOC_DAPM_SINGLE("Port2_2 Switch", LM49453_P0_DACLSR2_REG, 7, 1, 0), +SOC_DAPM_SINGLE("Sidetone Switch", LM49453_P0_STN_SEL_REG, 3, 0, 0), +}; + +static const struct snd_kcontrol_new lm49453_haptic_left_mixer[] = { +SOC_DAPM_SINGLE("Port1_1 Switch", LM49453_P0_DACHAL1_REG, 0, 1, 0), +SOC_DAPM_SINGLE("Port1_2 Switch", LM49453_P0_DACHAL1_REG, 1, 1, 0), +SOC_DAPM_SINGLE("Port1_3 Switch", LM49453_P0_DACHAL1_REG, 2, 1, 0), +SOC_DAPM_SINGLE("Port1_4 Switch", LM49453_P0_DACHAL1_REG, 3, 1, 0), +SOC_DAPM_SINGLE("Port1_5 Switch", LM49453_P0_DACHAL1_REG, 4, 1, 0), +SOC_DAPM_SINGLE("Port1_6 Switch", LM49453_P0_DACHAL1_REG, 5, 1, 0), +SOC_DAPM_SINGLE("Port1_7 Switch", LM49453_P0_DACHAL1_REG, 6, 1, 0), +SOC_DAPM_SINGLE("Port1_8 Switch", LM49453_P0_DACHAL1_REG, 7, 1, 0), +SOC_DAPM_SINGLE("DMIC1L Switch", LM49453_P0_DACHAL2_REG, 0, 1, 0), +SOC_DAPM_SINGLE("DMIC1R Switch", LM49453_P0_DACHAL2_REG, 1, 1, 0), +SOC_DAPM_SINGLE("DMIC2L Switch", LM49453_P0_DACHAL2_REG, 2, 1, 0), +SOC_DAPM_SINGLE("DMIC2R Switch", LM49453_P0_DACHAL2_REG, 3, 1, 0), +SOC_DAPM_SINGLE("ADCL Switch", LM49453_P0_DACHAL2_REG, 4, 1, 0), +SOC_DAPM_SINGLE("ADCR Switch", LM49453_P0_DACHAL2_REG, 5, 1, 0), +SOC_DAPM_SINGLE("Port2_1 Switch", LM49453_P0_DACHAL2_REG, 6, 1, 0), +SOC_DAPM_SINGLE("Port2_2 Switch", LM49453_P0_DACHAL2_REG, 7, 1, 0), +SOC_DAPM_SINGLE("Sidetone Switch", LM49453_P0_STN_SEL_REG, 4, 0, 0), +}; + +static const struct snd_kcontrol_new lm49453_haptic_right_mixer[] = { +SOC_DAPM_SINGLE("Port1_1 Switch", LM49453_P0_DACHAR1_REG, 0, 1, 0), +SOC_DAPM_SINGLE("Port1_2 Switch", LM49453_P0_DACHAR1_REG, 1, 1, 0), +SOC_DAPM_SINGLE("Port1_3 Switch", LM49453_P0_DACHAR1_REG, 2, 1, 0), +SOC_DAPM_SINGLE("Port1_4 Switch", LM49453_P0_DACHAR1_REG, 3, 1, 0), +SOC_DAPM_SINGLE("Port1_5 Switch", LM49453_P0_DACHAR1_REG, 4, 1, 0), +SOC_DAPM_SINGLE("Port1_6 Switch", LM49453_P0_DACHAR1_REG, 5, 1, 0), +SOC_DAPM_SINGLE("Port1_7 Switch", LM49453_P0_DACHAR1_REG, 6, 1, 0), +SOC_DAPM_SINGLE("Port1_8 Switch", LM49453_P0_DACHAR1_REG, 7, 1, 0), +SOC_DAPM_SINGLE("DMIC1L Switch", LM49453_P0_DACHAR2_REG, 0, 1, 0), +SOC_DAPM_SINGLE("DMIC1R Switch", LM49453_P0_DACHAR2_REG, 1, 1, 0), +SOC_DAPM_SINGLE("DMIC2L Switch", LM49453_P0_DACHAR2_REG, 2, 1, 0), +SOC_DAPM_SINGLE("DMIC2R Switch", LM49453_P0_DACHAR2_REG, 3, 1, 0), +SOC_DAPM_SINGLE("ADCL Switch", LM49453_P0_DACHAR2_REG, 4, 1, 0), +SOC_DAPM_SINGLE("ADCR Switch", LM49453_P0_DACHAR2_REG, 5, 1, 0), +SOC_DAPM_SINGLE("Port2_1 Switch", LM49453_P0_DACHAR2_REG, 6, 1, 0), +SOC_DAPM_SINGLE("Port2_2 Switch", LM49453_P0_DACHAR2_REG, 7, 1, 0), +SOC_DAPM_SINGLE("Sidetone Switch", LM49453_P0_STN_SEL_REG, 5, 0, 0), +}; + +static const struct snd_kcontrol_new lm49453_lineout_left_mixer[] = { +SOC_DAPM_SINGLE("Port1_1 Switch", LM49453_P0_DACLOL1_REG, 0, 1, 0), +SOC_DAPM_SINGLE("Port1_2 Switch", LM49453_P0_DACLOL1_REG, 1, 1, 0), +SOC_DAPM_SINGLE("Port1_3 Switch", LM49453_P0_DACLOL1_REG, 2, 1, 0), +SOC_DAPM_SINGLE("Port1_4 Switch", LM49453_P0_DACLOL1_REG, 3, 1, 0), +SOC_DAPM_SINGLE("Port1_5 Switch", LM49453_P0_DACLOL1_REG, 4, 1, 0), +SOC_DAPM_SINGLE("Port1_6 Switch", LM49453_P0_DACLOL1_REG, 5, 1, 0), +SOC_DAPM_SINGLE("Port1_7 Switch", LM49453_P0_DACLOL1_REG, 6, 1, 0), +SOC_DAPM_SINGLE("Port1_8 Switch", LM49453_P0_DACLOL1_REG, 7, 1, 0), +SOC_DAPM_SINGLE("DMIC1L Switch", LM49453_P0_DACLOL2_REG, 0, 1, 0), +SOC_DAPM_SINGLE("DMIC1R Switch", LM49453_P0_DACLOL2_REG, 1, 1, 0), +SOC_DAPM_SINGLE("DMIC2L Switch", LM49453_P0_DACLOL2_REG, 2, 1, 0), +SOC_DAPM_SINGLE("DMIC2R Switch", LM49453_P0_DACLOL2_REG, 3, 1, 0), +SOC_DAPM_SINGLE("ADCL Switch", LM49453_P0_DACLOL2_REG, 4, 1, 0), +SOC_DAPM_SINGLE("ADCR Switch", LM49453_P0_DACLOL2_REG, 5, 1, 0), +SOC_DAPM_SINGLE("Port2_1 Switch", LM49453_P0_DACLOL2_REG, 6, 1, 0), +SOC_DAPM_SINGLE("Port2_2 Switch", LM49453_P0_DACLOL2_REG, 7, 1, 0), +SOC_DAPM_SINGLE("Sidetone Switch", LM49453_P0_STN_SEL_REG, 6, 0, 0), +}; + +static const struct snd_kcontrol_new lm49453_lineout_right_mixer[] = { +SOC_DAPM_SINGLE("Port1_1 Switch", LM49453_P0_DACLOR1_REG, 0, 1, 0), +SOC_DAPM_SINGLE("Port1_2 Switch", LM49453_P0_DACLOR1_REG, 1, 1, 0), +SOC_DAPM_SINGLE("Port1_3 Switch", LM49453_P0_DACLOR1_REG, 2, 1, 0), +SOC_DAPM_SINGLE("Port1_4 Switch", LM49453_P0_DACLOR1_REG, 3, 1, 0), +SOC_DAPM_SINGLE("Port1_5 Switch", LM49453_P0_DACLOR1_REG, 4, 1, 0), +SOC_DAPM_SINGLE("Port1_6 Switch", LM49453_P0_DACLOR1_REG, 5, 1, 0), +SOC_DAPM_SINGLE("Port1_7 Switch", LM49453_P0_DACLOR1_REG, 6, 1, 0), +SOC_DAPM_SINGLE("Port1_8 Switch", LM49453_P0_DACLOR1_REG, 7, 1, 0), +SOC_DAPM_SINGLE("DMIC1L Switch", LM49453_P0_DACLOR2_REG, 0, 1, 0), +SOC_DAPM_SINGLE("DMIC1R Switch", LM49453_P0_DACLOR2_REG, 1, 1, 0), +SOC_DAPM_SINGLE("DMIC2L Switch", LM49453_P0_DACLOR2_REG, 2, 1, 0), +SOC_DAPM_SINGLE("DMIC2R Switch", LM49453_P0_DACLOR2_REG, 3, 1, 0), +SOC_DAPM_SINGLE("ADCL Switch", LM49453_P0_DACLOR2_REG, 4, 1, 0), +SOC_DAPM_SINGLE("ADCR Switch", LM49453_P0_DACLOR2_REG, 5, 1, 0), +SOC_DAPM_SINGLE("Port2_1 Switch", LM49453_P0_DACLOR2_REG, 6, 1, 0), +SOC_DAPM_SINGLE("Port2_2 Switch", LM49453_P0_DACLOR2_REG, 7, 1, 0), +SOC_DAPM_SINGLE("Sidetone Switch", LM49453_P0_STN_SEL_REG, 7, 0, 0), +}; + +static const struct snd_kcontrol_new lm49453_port1_tx1_mixer[] = { +SOC_DAPM_SINGLE("DMIC1L Switch", LM49453_P0_PORT1_TX1_REG, 0, 1, 0), +SOC_DAPM_SINGLE("DMIC1R Switch", LM49453_P0_PORT1_TX1_REG, 1, 1, 0), +SOC_DAPM_SINGLE("DMIC2L Switch", LM49453_P0_PORT1_TX1_REG, 2, 1, 0), +SOC_DAPM_SINGLE("DMIC2R Switch", LM49453_P0_PORT1_TX1_REG, 3, 1, 0), +SOC_DAPM_SINGLE("ADCL Switch", LM49453_P0_PORT1_TX1_REG, 4, 1, 0), +SOC_DAPM_SINGLE("ADCR Switch", LM49453_P0_PORT1_TX1_REG, 5, 1, 0), +SOC_DAPM_SINGLE("Port1_1 Switch", LM49453_P0_PORT1_TX1_REG, 6, 1, 0), +SOC_DAPM_SINGLE("Port2_1 Switch", LM49453_P0_PORT1_TX1_REG, 7, 1, 0), +}; + +static const struct snd_kcontrol_new lm49453_port1_tx2_mixer[] = { +SOC_DAPM_SINGLE("DMIC1L Switch", LM49453_P0_PORT1_TX2_REG, 0, 1, 0), +SOC_DAPM_SINGLE("DMIC1R Switch", LM49453_P0_PORT1_TX2_REG, 1, 1, 0), +SOC_DAPM_SINGLE("DMIC2L Switch", LM49453_P0_PORT1_TX2_REG, 2, 1, 0), +SOC_DAPM_SINGLE("DMIC2R Switch", LM49453_P0_PORT1_TX2_REG, 3, 1, 0), +SOC_DAPM_SINGLE("ADCL Switch", LM49453_P0_PORT1_TX2_REG, 4, 1, 0), +SOC_DAPM_SINGLE("ADCR Switch", LM49453_P0_PORT1_TX2_REG, 5, 1, 0), +SOC_DAPM_SINGLE("Port1_2 Switch", LM49453_P0_PORT1_TX2_REG, 6, 1, 0), +SOC_DAPM_SINGLE("Port2_2 Switch", LM49453_P0_PORT1_TX2_REG, 7, 1, 0), +}; + +static const struct snd_kcontrol_new lm49453_port1_tx3_mixer[] = { +SOC_DAPM_SINGLE("DMIC1L Switch", LM49453_P0_PORT1_TX3_REG, 0, 1, 0), +SOC_DAPM_SINGLE("DMIC1R Switch", LM49453_P0_PORT1_TX3_REG, 1, 1, 0), +SOC_DAPM_SINGLE("DMIC2L Switch", LM49453_P0_PORT1_TX3_REG, 2, 1, 0), +SOC_DAPM_SINGLE("DMIC2R Switch", LM49453_P0_PORT1_TX3_REG, 3, 1, 0), +SOC_DAPM_SINGLE("ADCL Switch", LM49453_P0_PORT1_TX3_REG, 4, 1, 0), +SOC_DAPM_SINGLE("ADCR Switch", LM49453_P0_PORT1_TX3_REG, 5, 1, 0), +SOC_DAPM_SINGLE("Port1_3 Switch", LM49453_P0_PORT1_TX3_REG, 6, 1, 0), +}; + +static const struct snd_kcontrol_new lm49453_port1_tx4_mixer[] = { +SOC_DAPM_SINGLE("DMIC1L Switch", LM49453_P0_PORT1_TX4_REG, 0, 1, 0), +SOC_DAPM_SINGLE("DMIC1R Switch", LM49453_P0_PORT1_TX4_REG, 1, 1, 0), +SOC_DAPM_SINGLE("DMIC2L Switch", LM49453_P0_PORT1_TX4_REG, 2, 1, 0), +SOC_DAPM_SINGLE("DMIC2R Switch", LM49453_P0_PORT1_TX4_REG, 3, 1, 0), +SOC_DAPM_SINGLE("ADCL Switch", LM49453_P0_PORT1_TX4_REG, 4, 1, 0), +SOC_DAPM_SINGLE("ADCR Switch", LM49453_P0_PORT1_TX4_REG, 5, 1, 0), +SOC_DAPM_SINGLE("Port1_4 Switch", LM49453_P0_PORT1_TX4_REG, 6, 1, 0), +}; + +static const struct snd_kcontrol_new lm49453_port1_tx5_mixer[] = { +SOC_DAPM_SINGLE("DMIC1L Switch", LM49453_P0_PORT1_TX5_REG, 0, 1, 0), +SOC_DAPM_SINGLE("DMIC1R Switch", LM49453_P0_PORT1_TX5_REG, 1, 1, 0), +SOC_DAPM_SINGLE("DMIC2L Switch", LM49453_P0_PORT1_TX5_REG, 2, 1, 0), +SOC_DAPM_SINGLE("DMIC2R Switch", LM49453_P0_PORT1_TX5_REG, 3, 1, 0), +SOC_DAPM_SINGLE("ADCL Switch", LM49453_P0_PORT1_TX5_REG, 4, 1, 0), +SOC_DAPM_SINGLE("ADCR Switch", LM49453_P0_PORT1_TX5_REG, 5, 1, 0), +SOC_DAPM_SINGLE("Port1_5 Switch", LM49453_P0_PORT1_TX5_REG, 6, 1, 0), +}; + +static const struct snd_kcontrol_new lm49453_port1_tx6_mixer[] = { +SOC_DAPM_SINGLE("DMIC1L Switch", LM49453_P0_PORT1_TX6_REG, 0, 1, 0), +SOC_DAPM_SINGLE("DMIC1R Switch", LM49453_P0_PORT1_TX6_REG, 1, 1, 0), +SOC_DAPM_SINGLE("DMIC2L Switch", LM49453_P0_PORT1_TX6_REG, 2, 1, 0), +SOC_DAPM_SINGLE("DMIC2R Switch", LM49453_P0_PORT1_TX6_REG, 3, 1, 0), +SOC_DAPM_SINGLE("ADCL Switch", LM49453_P0_PORT1_TX6_REG, 4, 1, 0), +SOC_DAPM_SINGLE("ADCR Switch", LM49453_P0_PORT1_TX6_REG, 5, 1, 0), +SOC_DAPM_SINGLE("Port1_6 Switch", LM49453_P0_PORT1_TX6_REG, 6, 1, 0), +}; + +static const struct snd_kcontrol_new lm49453_port1_tx7_mixer[] = { +SOC_DAPM_SINGLE("DMIC1L Switch", LM49453_P0_PORT1_TX7_REG, 0, 1, 0), +SOC_DAPM_SINGLE("DMIC1R Switch", LM49453_P0_PORT1_TX7_REG, 1, 1, 0), +SOC_DAPM_SINGLE("DMIC2L Switch", LM49453_P0_PORT1_TX7_REG, 2, 1, 0), +SOC_DAPM_SINGLE("DMIC2R Switch", LM49453_P0_PORT1_TX7_REG, 3, 1, 0), +SOC_DAPM_SINGLE("ADCL Switch", LM49453_P0_PORT1_TX7_REG, 4, 1, 0), +SOC_DAPM_SINGLE("ADCR Switch", LM49453_P0_PORT1_TX7_REG, 5, 1, 0), +SOC_DAPM_SINGLE("Port1_7 Switch", LM49453_P0_PORT1_TX7_REG, 6, 1, 0), +}; + +static const struct snd_kcontrol_new lm49453_port1_tx8_mixer[] = { +SOC_DAPM_SINGLE("DMIC1L Switch", LM49453_P0_PORT1_TX8_REG, 0, 1, 0), +SOC_DAPM_SINGLE("DMIC1R Switch", LM49453_P0_PORT1_TX8_REG, 1, 1, 0), +SOC_DAPM_SINGLE("DMIC2L Switch", LM49453_P0_PORT1_TX8_REG, 2, 1, 0), +SOC_DAPM_SINGLE("DMIC2R Switch", LM49453_P0_PORT1_TX8_REG, 3, 1, 0), +SOC_DAPM_SINGLE("ADCL Switch", LM49453_P0_PORT1_TX8_REG, 4, 1, 0), +SOC_DAPM_SINGLE("ADCR Switch", LM49453_P0_PORT1_TX8_REG, 5, 1, 0), +SOC_DAPM_SINGLE("Port1_8 Switch", LM49453_P0_PORT1_TX8_REG, 6, 1, 0), +}; + +static const struct snd_kcontrol_new lm49453_port2_tx1_mixer[] = { +SOC_DAPM_SINGLE("DMIC1L Switch", LM49453_P0_PORT2_TX1_REG, 0, 1, 0), +SOC_DAPM_SINGLE("DMIC1R Switch", LM49453_P0_PORT2_TX1_REG, 1, 1, 0), +SOC_DAPM_SINGLE("DMIC2L Switch", LM49453_P0_PORT2_TX1_REG, 2, 1, 0), +SOC_DAPM_SINGLE("DMIC2R Switch", LM49453_P0_PORT2_TX1_REG, 3, 1, 0), +SOC_DAPM_SINGLE("ADCL Switch", LM49453_P0_PORT2_TX1_REG, 4, 1, 0), +SOC_DAPM_SINGLE("ADCR Switch", LM49453_P0_PORT2_TX1_REG, 5, 1, 0), +SOC_DAPM_SINGLE("Port1_1 Switch", LM49453_P0_PORT2_TX1_REG, 6, 1, 0), +SOC_DAPM_SINGLE("Port2_1 Switch", LM49453_P0_PORT2_TX1_REG, 7, 1, 0), +}; + +static const struct snd_kcontrol_new lm49453_port2_tx2_mixer[] = { +SOC_DAPM_SINGLE("DMIC1L Switch", LM49453_P0_PORT2_TX2_REG, 0, 1, 0), +SOC_DAPM_SINGLE("DMIC1R Switch", LM49453_P0_PORT2_TX2_REG, 1, 1, 0), +SOC_DAPM_SINGLE("DMIC2L Switch", LM49453_P0_PORT2_TX2_REG, 2, 1, 0), +SOC_DAPM_SINGLE("DMIC2R Switch", LM49453_P0_PORT2_TX2_REG, 3, 1, 0), +SOC_DAPM_SINGLE("ADCL Switch", LM49453_P0_PORT2_TX2_REG, 4, 1, 0), +SOC_DAPM_SINGLE("ADCR Switch", LM49453_P0_PORT2_TX2_REG, 5, 1, 0), +SOC_DAPM_SINGLE("Port1_2 Switch", LM49453_P0_PORT2_TX2_REG, 6, 1, 0), +SOC_DAPM_SINGLE("Port2_2 Switch", LM49453_P0_PORT2_TX2_REG, 7, 1, 0), +}; + +/* TLV Declarations */ +static const DECLARE_TLV_DB_SCALE(digital_tlv, -7650, 150, 1); +static const DECLARE_TLV_DB_SCALE(port_tlv, 0, 600, 0); + +static const struct snd_kcontrol_new lm49453_sidetone_mixer_controls[] = { +/* Sidetone supports mono only */ +SOC_DAPM_SINGLE_TLV("Sidetone ADCL Volume", LM49453_P0_STN_VOL_ADCL_REG, + 0, 0x3F, 0, digital_tlv), +SOC_DAPM_SINGLE_TLV("Sidetone ADCR Volume", LM49453_P0_STN_VOL_ADCR_REG, + 0, 0x3F, 0, digital_tlv), +SOC_DAPM_SINGLE_TLV("Sidetone DMIC1L Volume", LM49453_P0_STN_VOL_DMIC1L_REG, + 0, 0x3F, 0, digital_tlv), +SOC_DAPM_SINGLE_TLV("Sidetone DMIC1R Volume", LM49453_P0_STN_VOL_DMIC1R_REG, + 0, 0x3F, 0, digital_tlv), +SOC_DAPM_SINGLE_TLV("Sidetone DMIC2L Volume", LM49453_P0_STN_VOL_DMIC2L_REG, + 0, 0x3F, 0, digital_tlv), +SOC_DAPM_SINGLE_TLV("Sidetone DMIC2R Volume", LM49453_P0_STN_VOL_DMIC2R_REG, + 0, 0x3F, 0, digital_tlv), +}; + +static const struct snd_kcontrol_new lm49453_snd_controls[] = { + /* mic1 and mic2 supports mono only */ + SOC_SINGLE_TLV("Mic1 Volume", LM49453_P0_ADC_LEVELL_REG, 0, 6, + 0, digital_tlv), + SOC_SINGLE_TLV("Mic2 Volume", LM49453_P0_ADC_LEVELR_REG, 0, 6, + 0, digital_tlv), + + SOC_DOUBLE_R_TLV("DMIC1 Volume", LM49453_P0_DMIC1_LEVELL_REG, + LM49453_P0_DMIC1_LEVELR_REG, 0, 6, 0, digital_tlv), + SOC_DOUBLE_R_TLV("DMIC2 Volume", LM49453_P0_DMIC2_LEVELL_REG, + LM49453_P0_DMIC2_LEVELR_REG, 0, 6, 0, digital_tlv), + + SOC_DAPM_ENUM("Mic2Mode", lm49453_mic2mode_enum), + SOC_DAPM_ENUM("DMIC12 SRC", lm49453_dmic12_cfg_enum), + SOC_DAPM_ENUM("DMIC34 SRC", lm49453_dmic34_cfg_enum), + + /* Capture path filter enable */ + SOC_SINGLE("DMIC1 HPFilter Switch", LM49453_P0_ADC_FX_ENABLES_REG, + 0, 1, 0), + SOC_SINGLE("DMIC2 HPFilter Switch", LM49453_P0_ADC_FX_ENABLES_REG, + 1, 1, 0), + SOC_SINGLE("ADC HPFilter Switch", LM49453_P0_ADC_FX_ENABLES_REG, + 2, 1, 0), + + SOC_DOUBLE_R_TLV("DAC HP Volume", LM49453_P0_DAC_HP_LEVELL_REG, + LM49453_P0_DAC_HP_LEVELR_REG, 0, 6, 0, digital_tlv), + SOC_DOUBLE_R_TLV("DAC LO Volume", LM49453_P0_DAC_LO_LEVELL_REG, + LM49453_P0_DAC_LO_LEVELR_REG, 0, 6, 0, digital_tlv), + SOC_DOUBLE_R_TLV("DAC LS Volume", LM49453_P0_DAC_LS_LEVELL_REG, + LM49453_P0_DAC_LS_LEVELR_REG, 0, 6, 0, digital_tlv), + SOC_DOUBLE_R_TLV("DAC HA Volume", LM49453_P0_DAC_HA_LEVELL_REG, + LM49453_P0_DAC_HA_LEVELR_REG, 0, 6, 0, digital_tlv), + + SOC_SINGLE_TLV("EP Volume", LM49453_P0_DAC_LS_LEVELL_REG, + 0, 6, 0, digital_tlv), + + SOC_SINGLE_TLV("PORT1_1_RX_LVL Volume", LM49453_P0_PORT1_RX_LVL1_REG, + 0, 3, 0, port_tlv), + SOC_SINGLE_TLV("PORT1_2_RX_LVL Volume", LM49453_P0_PORT1_RX_LVL1_REG, + 2, 3, 0, port_tlv), + SOC_SINGLE_TLV("PORT1_3_RX_LVL Volume", LM49453_P0_PORT1_RX_LVL1_REG, + 4, 3, 0, port_tlv), + SOC_SINGLE_TLV("PORT1_4_RX_LVL Volume", LM49453_P0_PORT1_RX_LVL1_REG, + 6, 3, 0, port_tlv), + SOC_SINGLE_TLV("PORT1_5_RX_LVL Volume", LM49453_P0_PORT1_RX_LVL2_REG, + 0, 3, 0, port_tlv), + SOC_SINGLE_TLV("PORT1_6_RX_LVL Volume", LM49453_P0_PORT1_RX_LVL2_REG, + 2, 3, 0, port_tlv), + SOC_SINGLE_TLV("PORT1_7_RX_LVL Volume", LM49453_P0_PORT1_RX_LVL2_REG, + 4, 3, 0, port_tlv), + SOC_SINGLE_TLV("PORT1_8_RX_LVL Volume", LM49453_P0_PORT1_RX_LVL2_REG, + 6, 3, 0, port_tlv), + + SOC_SINGLE_TLV("PORT2_1_RX_LVL Volume", LM49453_P0_PORT2_RX_LVL_REG, + 0, 3, 0, port_tlv), + SOC_SINGLE_TLV("PORT2_2_RX_LVL Volume", LM49453_P0_PORT2_RX_LVL_REG, + 2, 3, 0, port_tlv), + + SOC_SINGLE("Port1 Playback Switch", LM49453_P0_AUDIO_PORT1_BASIC_REG, + 1, 1, 0), + SOC_SINGLE("Port2 Playback Switch", LM49453_P0_AUDIO_PORT2_BASIC_REG, + 1, 1, 0), + SOC_SINGLE("Port1 Capture Switch", LM49453_P0_AUDIO_PORT1_BASIC_REG, + 2, 1, 0), + SOC_SINGLE("Port2 Capture Switch", LM49453_P0_AUDIO_PORT2_BASIC_REG, + 2, 1, 0) + +}; + +/* DAPM widgets */ +static const struct snd_soc_dapm_widget lm49453_dapm_widgets[] = { + + /* All end points HP,EP, LS, Lineout and Haptic */ + SND_SOC_DAPM_OUTPUT("HPOUTL"), + SND_SOC_DAPM_OUTPUT("HPOUTR"), + SND_SOC_DAPM_OUTPUT("EPOUT"), + SND_SOC_DAPM_OUTPUT("LSOUTL"), + SND_SOC_DAPM_OUTPUT("LSOUTR"), + SND_SOC_DAPM_OUTPUT("LOOUTR"), + SND_SOC_DAPM_OUTPUT("LOOUTL"), + SND_SOC_DAPM_OUTPUT("HAOUTL"), + SND_SOC_DAPM_OUTPUT("HAOUTR"), + + SND_SOC_DAPM_INPUT("AMIC1"), + SND_SOC_DAPM_INPUT("AMIC2"), + SND_SOC_DAPM_INPUT("DMIC1DAT"), + SND_SOC_DAPM_INPUT("DMIC2DAT"), + SND_SOC_DAPM_INPUT("AUXL"), + SND_SOC_DAPM_INPUT("AUXR"), + + SND_SOC_DAPM_PGA("PORT1_1_RX", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("PORT1_2_RX", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("PORT1_3_RX", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("PORT1_4_RX", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("PORT1_5_RX", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("PORT1_6_RX", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("PORT1_7_RX", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("PORT1_8_RX", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("PORT2_1_RX", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("PORT2_2_RX", SND_SOC_NOPM, 0, 0, NULL, 0), + + SND_SOC_DAPM_SUPPLY("AMIC1Bias", LM49453_P0_MICL_REG, 6, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("AMIC2Bias", LM49453_P0_MICR_REG, 6, 0, NULL, 0), + + /* playback path driver enables */ + SND_SOC_DAPM_OUT_DRV("Headset Switch", + LM49453_P0_PMC_SETUP_REG, 0, 0, NULL, 0), + SND_SOC_DAPM_OUT_DRV("Earpiece Switch", + LM49453_P0_EP_REG, 0, 0, NULL, 0), + SND_SOC_DAPM_OUT_DRV("Speaker Left Switch", + LM49453_P0_DIS_PKVL_FB_REG, 0, 1, NULL, 0), + SND_SOC_DAPM_OUT_DRV("Speaker Right Switch", + LM49453_P0_DIS_PKVL_FB_REG, 1, 1, NULL, 0), + SND_SOC_DAPM_OUT_DRV("Haptic Left Switch", + LM49453_P0_DIS_PKVL_FB_REG, 2, 1, NULL, 0), + SND_SOC_DAPM_OUT_DRV("Haptic Right Switch", + LM49453_P0_DIS_PKVL_FB_REG, 3, 1, NULL, 0), + + /* DAC */ + SND_SOC_DAPM_DAC("HPL DAC", "Headset", SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_DAC("HPR DAC", "Headset", SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_DAC("LSL DAC", "Speaker", SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_DAC("LSR DAC", "Speaker", SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_DAC("HAL DAC", "Haptic", SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_DAC("HAR DAC", "Haptic", SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_DAC("LOL DAC", "Lineout", SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_DAC("LOR DAC", "Lineout", SND_SOC_NOPM, 0, 0), + + + SND_SOC_DAPM_PGA("AUXL Input", + LM49453_P0_ANALOG_MIXER_ADC_REG, 2, 0, NULL, 0), + SND_SOC_DAPM_PGA("AUXR Input", + LM49453_P0_ANALOG_MIXER_ADC_REG, 3, 0, NULL, 0), + + SND_SOC_DAPM_PGA("Sidetone", SND_SOC_NOPM, 0, 0, NULL, 0), + + /* ADC */ + SND_SOC_DAPM_ADC("DMIC1 Left", "Capture", SND_SOC_NOPM, 1, 0), + SND_SOC_DAPM_ADC("DMIC1 Right", "Capture", SND_SOC_NOPM, 1, 0), + SND_SOC_DAPM_ADC("DMIC2 Left", "Capture", SND_SOC_NOPM, 1, 0), + SND_SOC_DAPM_ADC("DMIC2 Right", "Capture", SND_SOC_NOPM, 1, 0), + + SND_SOC_DAPM_ADC("ADC Left", "Capture", SND_SOC_NOPM, 1, 0), + SND_SOC_DAPM_ADC("ADC Right", "Capture", SND_SOC_NOPM, 0, 0), + + SND_SOC_DAPM_MUX("ADCL Mux", SND_SOC_NOPM, 0, 0, + &lm49453_adcl_mux_control), + SND_SOC_DAPM_MUX("ADCR Mux", SND_SOC_NOPM, 0, 0, + &lm49453_adcr_mux_control), + + SND_SOC_DAPM_MUX("Mic1 Input", + SND_SOC_NOPM, 0, 0, &lm49453_adcl_mux_control), + + SND_SOC_DAPM_MUX("Mic2 Input", + SND_SOC_NOPM, 0, 0, &lm49453_adcr_mux_control), + + /* AIF */ + SND_SOC_DAPM_AIF_IN("PORT1_SDI", NULL, 0, + LM49453_P0_PULL_CONFIG1_REG, 2, 0), + SND_SOC_DAPM_AIF_IN("PORT2_SDI", NULL, 0, + LM49453_P0_PULL_CONFIG1_REG, 6, 0), + + SND_SOC_DAPM_AIF_OUT("PORT1_SDO", NULL, 0, + LM49453_P0_PULL_CONFIG1_REG, 3, 0), + SND_SOC_DAPM_AIF_OUT("PORT2_SDO", NULL, 0, + LM49453_P0_PULL_CONFIG1_REG, 7, 0), + + /* Port1 TX controls */ + SND_SOC_DAPM_OUT_DRV("P1_1_TX", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_OUT_DRV("P1_2_TX", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_OUT_DRV("P1_3_TX", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_OUT_DRV("P1_4_TX", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_OUT_DRV("P1_5_TX", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_OUT_DRV("P1_6_TX", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_OUT_DRV("P1_7_TX", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_OUT_DRV("P1_8_TX", SND_SOC_NOPM, 0, 0, NULL, 0), + + /* Port2 TX controls */ + SND_SOC_DAPM_OUT_DRV("P2_1_TX", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_OUT_DRV("P2_2_TX", SND_SOC_NOPM, 0, 0, NULL, 0), + + /* Sidetone Mixer */ + SND_SOC_DAPM_MIXER("Sidetone Mixer", SND_SOC_NOPM, 0, 0, + lm49453_sidetone_mixer_controls, + ARRAY_SIZE(lm49453_sidetone_mixer_controls)), + + /* DAC MIXERS */ + SND_SOC_DAPM_MIXER("HPL Mixer", SND_SOC_NOPM, 0, 0, + lm49453_headset_left_mixer, + ARRAY_SIZE(lm49453_headset_left_mixer)), + SND_SOC_DAPM_MIXER("HPR Mixer", SND_SOC_NOPM, 0, 0, + lm49453_headset_right_mixer, + ARRAY_SIZE(lm49453_headset_right_mixer)), + SND_SOC_DAPM_MIXER("LOL Mixer", SND_SOC_NOPM, 0, 0, + lm49453_lineout_left_mixer, + ARRAY_SIZE(lm49453_lineout_left_mixer)), + SND_SOC_DAPM_MIXER("LOR Mixer", SND_SOC_NOPM, 0, 0, + lm49453_lineout_right_mixer, + ARRAY_SIZE(lm49453_lineout_right_mixer)), + SND_SOC_DAPM_MIXER("LSL Mixer", SND_SOC_NOPM, 0, 0, + lm49453_speaker_left_mixer, + ARRAY_SIZE(lm49453_speaker_left_mixer)), + SND_SOC_DAPM_MIXER("LSR Mixer", SND_SOC_NOPM, 0, 0, + lm49453_speaker_right_mixer, + ARRAY_SIZE(lm49453_speaker_right_mixer)), + SND_SOC_DAPM_MIXER("HAL Mixer", SND_SOC_NOPM, 0, 0, + lm49453_haptic_left_mixer, + ARRAY_SIZE(lm49453_haptic_left_mixer)), + SND_SOC_DAPM_MIXER("HAR Mixer", SND_SOC_NOPM, 0, 0, + lm49453_haptic_right_mixer, + ARRAY_SIZE(lm49453_haptic_right_mixer)), + + /* Capture Mixer */ + SND_SOC_DAPM_MIXER("Port1_1 Mixer", SND_SOC_NOPM, 0, 0, + lm49453_port1_tx1_mixer, + ARRAY_SIZE(lm49453_port1_tx1_mixer)), + SND_SOC_DAPM_MIXER("Port1_2 Mixer", SND_SOC_NOPM, 0, 0, + lm49453_port1_tx2_mixer, + ARRAY_SIZE(lm49453_port1_tx2_mixer)), + SND_SOC_DAPM_MIXER("Port1_3 Mixer", SND_SOC_NOPM, 0, 0, + lm49453_port1_tx3_mixer, + ARRAY_SIZE(lm49453_port1_tx3_mixer)), + SND_SOC_DAPM_MIXER("Port1_4 Mixer", SND_SOC_NOPM, 0, 0, + lm49453_port1_tx4_mixer, + ARRAY_SIZE(lm49453_port1_tx4_mixer)), + SND_SOC_DAPM_MIXER("Port1_5 Mixer", SND_SOC_NOPM, 0, 0, + lm49453_port1_tx5_mixer, + ARRAY_SIZE(lm49453_port1_tx5_mixer)), + SND_SOC_DAPM_MIXER("Port1_6 Mixer", SND_SOC_NOPM, 0, 0, + lm49453_port1_tx6_mixer, + ARRAY_SIZE(lm49453_port1_tx6_mixer)), + SND_SOC_DAPM_MIXER("Port1_7 Mixer", SND_SOC_NOPM, 0, 0, + lm49453_port1_tx7_mixer, + ARRAY_SIZE(lm49453_port1_tx7_mixer)), + SND_SOC_DAPM_MIXER("Port1_8 Mixer", SND_SOC_NOPM, 0, 0, + lm49453_port1_tx8_mixer, + ARRAY_SIZE(lm49453_port1_tx8_mixer)), + + SND_SOC_DAPM_MIXER("Port2_1 Mixer", SND_SOC_NOPM, 0, 0, + lm49453_port2_tx1_mixer, + ARRAY_SIZE(lm49453_port2_tx1_mixer)), + SND_SOC_DAPM_MIXER("Port2_2 Mixer", SND_SOC_NOPM, 0, 0, + lm49453_port2_tx2_mixer, + ARRAY_SIZE(lm49453_port2_tx2_mixer)), +}; + +static const struct snd_soc_dapm_route lm49453_audio_map[] = { + /* Port SDI mapping */ + { "PORT1_1_RX", "Port1 Playback Switch", "PORT1_SDI" }, + { "PORT1_2_RX", "Port1 Playback Switch", "PORT1_SDI" }, + { "PORT1_3_RX", "Port1 Playback Switch", "PORT1_SDI" }, + { "PORT1_4_RX", "Port1 Playback Switch", "PORT1_SDI" }, + { "PORT1_5_RX", "Port1 Playback Switch", "PORT1_SDI" }, + { "PORT1_6_RX", "Port1 Playback Switch", "PORT1_SDI" }, + { "PORT1_7_RX", "Port1 Playback Switch", "PORT1_SDI" }, + { "PORT1_8_RX", "Port1 Playback Switch", "PORT1_SDI" }, + + { "PORT2_1_RX", "Port2 Playback Switch", "PORT2_SDI" }, + { "PORT2_2_RX", "Port2 Playback Switch", "PORT2_SDI" }, + + /* HP mapping */ + { "HPL Mixer", "Port1_1 Switch", "PORT1_1_RX" }, + { "HPL Mixer", "Port1_2 Switch", "PORT1_2_RX" }, + { "HPL Mixer", "Port1_3 Switch", "PORT1_3_RX" }, + { "HPL Mixer", "Port1_4 Switch", "PORT1_4_RX" }, + { "HPL Mixer", "Port1_5 Switch", "PORT1_5_RX" }, + { "HPL Mixer", "Port1_6 Switch", "PORT1_6_RX" }, + { "HPL Mixer", "Port1_7 Switch", "PORT1_7_RX" }, + { "HPL Mixer", "Port1_8 Switch", "PORT1_8_RX" }, + + { "HPL Mixer", "Port2_1 Switch", "PORT2_1_RX" }, + { "HPL Mixer", "Port2_2 Switch", "PORT2_2_RX" }, + + { "HPL Mixer", "ADCL Switch", "ADC Left" }, + { "HPL Mixer", "ADCR Switch", "ADC Right" }, + { "HPL Mixer", "DMIC1L Switch", "DMIC1 Left" }, + { "HPL Mixer", "DMIC1R Switch", "DMIC1 Right" }, + { "HPL Mixer", "DMIC2L Switch", "DMIC2 Left" }, + { "HPL Mixer", "DMIC2R Switch", "DMIC2 Right" }, + { "HPL Mixer", "Sidetone Switch", "Sidetone" }, + + { "HPL DAC", NULL, "HPL Mixer" }, + + { "HPR Mixer", "Port1_1 Switch", "PORT1_1_RX" }, + { "HPR Mixer", "Port1_2 Switch", "PORT1_2_RX" }, + { "HPR Mixer", "Port1_3 Switch", "PORT1_3_RX" }, + { "HPR Mixer", "Port1_4 Switch", "PORT1_4_RX" }, + { "HPR Mixer", "Port1_5 Switch", "PORT1_5_RX" }, + { "HPR Mixer", "Port1_6 Switch", "PORT1_6_RX" }, + { "HPR Mixer", "Port1_7 Switch", "PORT1_7_RX" }, + { "HPR Mixer", "Port1_8 Switch", "PORT1_8_RX" }, + + /* Port 2 */ + { "HPR Mixer", "Port2_1 Switch", "PORT2_1_RX" }, + { "HPR Mixer", "Port2_2 Switch", "PORT2_2_RX" }, + + { "HPR Mixer", "ADCL Switch", "ADC Left" }, + { "HPR Mixer", "ADCR Switch", "ADC Right" }, + { "HPR Mixer", "DMIC1L Switch", "DMIC1 Left" }, + { "HPR Mixer", "DMIC1R Switch", "DMIC1 Right" }, + { "HPR Mixer", "DMIC2L Switch", "DMIC2 Left" }, + { "HPR Mixer", "DMIC2L Switch", "DMIC2 Right" }, + { "HPR Mixer", "Sidetone Switch", "Sidetone" }, + + { "HPR DAC", NULL, "HPR Mixer" }, + + { "HPOUTL", "Headset Switch", "HPL DAC"}, + { "HPOUTR", "Headset Switch", "HPR DAC"}, + + /* EP map */ + { "EPOUT", "Earpiece Switch", "HPL DAC" }, + + /* Speaker map */ + { "LSL Mixer", "Port1_1 Switch", "PORT1_1_RX" }, + { "LSL Mixer", "Port1_2 Switch", "PORT1_2_RX" }, + { "LSL Mixer", "Port1_3 Switch", "PORT1_3_RX" }, + { "LSL Mixer", "Port1_4 Switch", "PORT1_4_RX" }, + { "LSL Mixer", "Port1_5 Switch", "PORT1_5_RX" }, + { "LSL Mixer", "Port1_6 Switch", "PORT1_6_RX" }, + { "LSL Mixer", "Port1_7 Switch", "PORT1_7_RX" }, + { "LSL Mixer", "Port1_8 Switch", "PORT1_8_RX" }, + + /* Port 2 */ + { "LSL Mixer", "Port2_1 Switch", "PORT2_1_RX" }, + { "LSL Mixer", "Port2_2 Switch", "PORT2_2_RX" }, + + { "LSL Mixer", "ADCL Switch", "ADC Left" }, + { "LSL Mixer", "ADCR Switch", "ADC Right" }, + { "LSL Mixer", "DMIC1L Switch", "DMIC1 Left" }, + { "LSL Mixer", "DMIC1R Switch", "DMIC1 Right" }, + { "LSL Mixer", "DMIC2L Switch", "DMIC2 Left" }, + { "LSL Mixer", "DMIC2R Switch", "DMIC2 Right" }, + { "LSL Mixer", "Sidetone Switch", "Sidetone" }, + + { "LSL DAC", NULL, "LSL Mixer" }, + + { "LSR Mixer", "Port1_1 Switch", "PORT1_1_RX" }, + { "LSR Mixer", "Port1_2 Switch", "PORT1_2_RX" }, + { "LSR Mixer", "Port1_3 Switch", "PORT1_3_RX" }, + { "LSR Mixer", "Port1_4 Switch", "PORT1_4_RX" }, + { "LSR Mixer", "Port1_5 Switch", "PORT1_5_RX" }, + { "LSR Mixer", "Port1_6 Switch", "PORT1_6_RX" }, + { "LSR Mixer", "Port1_7 Switch", "PORT1_7_RX" }, + { "LSR Mixer", "Port1_8 Switch", "PORT1_8_RX" }, + + /* Port 2 */ + { "LSR Mixer", "Port2_1 Switch", "PORT2_1_RX" }, + { "LSR Mixer", "Port2_2 Switch", "PORT2_2_RX" }, + + { "LSR Mixer", "ADCL Switch", "ADC Left" }, + { "LSR Mixer", "ADCR Switch", "ADC Right" }, + { "LSR Mixer", "DMIC1L Switch", "DMIC1 Left" }, + { "LSR Mixer", "DMIC1R Switch", "DMIC1 Right" }, + { "LSR Mixer", "DMIC2L Switch", "DMIC2 Left" }, + { "LSR Mixer", "DMIC2R Switch", "DMIC2 Right" }, + { "LSR Mixer", "Sidetone Switch", "Sidetone" }, + + { "LSR DAC", NULL, "LSR Mixer" }, + + { "LSOUTL", "Speaker Left Switch", "LSL DAC"}, + { "LSOUTR", "Speaker Left Switch", "LSR DAC"}, + + /* Haptic map */ + { "HAL Mixer", "Port1_1 Switch", "PORT1_1_RX" }, + { "HAL Mixer", "Port1_2 Switch", "PORT1_2_RX" }, + { "HAL Mixer", "Port1_3 Switch", "PORT1_3_RX" }, + { "HAL Mixer", "Port1_4 Switch", "PORT1_4_RX" }, + { "HAL Mixer", "Port1_5 Switch", "PORT1_5_RX" }, + { "HAL Mixer", "Port1_6 Switch", "PORT1_6_RX" }, + { "HAL Mixer", "Port1_7 Switch", "PORT1_7_RX" }, + { "HAL Mixer", "Port1_8 Switch", "PORT1_8_RX" }, + + /* Port 2 */ + { "HAL Mixer", "Port2_1 Switch", "PORT2_1_RX" }, + { "HAL Mixer", "Port2_2 Switch", "PORT2_2_RX" }, + + { "HAL Mixer", "ADCL Switch", "ADC Left" }, + { "HAL Mixer", "ADCR Switch", "ADC Right" }, + { "HAL Mixer", "DMIC1L Switch", "DMIC1 Left" }, + { "HAL Mixer", "DMIC1R Switch", "DMIC1 Right" }, + { "HAL Mixer", "DMIC2L Switch", "DMIC2 Left" }, + { "HAL Mixer", "DMIC2R Switch", "DMIC2 Right" }, + { "HAL Mixer", "Sidetone Switch", "Sidetone" }, + + { "HAL DAC", NULL, "HAL Mixer" }, + + { "HAR Mixer", "Port1_1 Switch", "PORT1_1_RX" }, + { "HAR Mixer", "Port1_2 Switch", "PORT1_2_RX" }, + { "HAR Mixer", "Port1_3 Switch", "PORT1_3_RX" }, + { "HAR Mixer", "Port1_4 Switch", "PORT1_4_RX" }, + { "HAR Mixer", "Port1_5 Switch", "PORT1_5_RX" }, + { "HAR Mixer", "Port1_6 Switch", "PORT1_6_RX" }, + { "HAR Mixer", "Port1_7 Switch", "PORT1_7_RX" }, + { "HAR Mixer", "Port1_8 Switch", "PORT1_8_RX" }, + + /* Port 2 */ + { "HAR Mixer", "Port2_1 Switch", "PORT2_1_RX" }, + { "HAR Mixer", "Port2_2 Switch", "PORT2_2_RX" }, + + { "HAR Mixer", "ADCL Switch", "ADC Left" }, + { "HAR Mixer", "ADCR Switch", "ADC Right" }, + { "HAR Mixer", "DMIC1L Switch", "DMIC1 Left" }, + { "HAR Mixer", "DMIC1R Switch", "DMIC1 Right" }, + { "HAR Mixer", "DMIC2L Switch", "DMIC2 Left" }, + { "HAR Mixer", "DMIC2R Switch", "DMIC2 Right" }, + { "HAR Mixer", "Sideton Switch", "Sidetone" }, + + { "HAR DAC", NULL, "HAR Mixer" }, + + { "HAOUTL", "Haptic Left Switch", "HAL DAC" }, + { "HAOUTR", "Haptic Right Switch", "HAR DAC" }, + + /* Lineout map */ + { "LOL Mixer", "Port1_1 Switch", "PORT1_1_RX" }, + { "LOL Mixer", "Port1_2 Switch", "PORT1_2_RX" }, + { "LOL Mixer", "Port1_3 Switch", "PORT1_3_RX" }, + { "LOL Mixer", "Port1_4 Switch", "PORT1_4_RX" }, + { "LOL Mixer", "Port1_5 Switch", "PORT1_5_RX" }, + { "LOL Mixer", "Port1_6 Switch", "PORT1_6_RX" }, + { "LOL Mixer", "Port1_7 Switch", "PORT1_7_RX" }, + { "LOL Mixer", "Port1_8 Switch", "PORT1_8_RX" }, + + /* Port 2 */ + { "LOL Mixer", "Port2_1 Switch", "PORT2_1_RX" }, + { "LOL Mixer", "Port2_2 Switch", "PORT2_2_RX" }, + + { "LOL Mixer", "ADCL Switch", "ADC Left" }, + { "LOL Mixer", "ADCR Switch", "ADC Right" }, + { "LOL Mixer", "DMIC1L Switch", "DMIC1 Left" }, + { "LOL Mixer", "DMIC1R Switch", "DMIC1 Right" }, + { "LOL Mixer", "DMIC2L Switch", "DMIC2 Left" }, + { "LOL Mixer", "DMIC2R Switch", "DMIC2 Right" }, + { "LOL Mixer", "Sidetone Switch", "Sidetone" }, + + { "LOL DAC", NULL, "LOL Mixer" }, + + { "LOR Mixer", "Port1_1 Switch", "PORT1_1_RX" }, + { "LOR Mixer", "Port1_2 Switch", "PORT1_2_RX" }, + { "LOR Mixer", "Port1_3 Switch", "PORT1_3_RX" }, + { "LOR Mixer", "Port1_4 Switch", "PORT1_4_RX" }, + { "LOR Mixer", "Port1_5 Switch", "PORT1_5_RX" }, + { "LOR Mixer", "Port1_6 Switch", "PORT1_6_RX" }, + { "LOR Mixer", "Port1_7 Switch", "PORT1_7_RX" }, + { "LOR Mixer", "Port1_8 Switch", "PORT1_8_RX" }, + + /* Port 2 */ + { "LOR Mixer", "Port2_1 Switch", "PORT2_1_RX" }, + { "LOR Mixer", "Port2_2 Switch", "PORT2_2_RX" }, + + { "LOR Mixer", "ADCL Switch", "ADC Left" }, + { "LOR Mixer", "ADCR Switch", "ADC Right" }, + { "LOR Mixer", "DMIC1L Switch", "DMIC1 Left" }, + { "LOR Mixer", "DMIC1R Switch", "DMIC1 Right" }, + { "LOR Mixer", "DMIC2L Switch", "DMIC2 Left" }, + { "LOR Mixer", "DMIC2R Switch", "DMIC2 Right" }, + { "LOR Mixer", "Sidetone Switch", "Sidetone" }, + + { "LOR DAC", NULL, "LOR Mixer" }, + + { "LOOUTL", NULL, "LOL DAC" }, + { "LOOUTR", NULL, "LOR DAC" }, + + /* TX map */ + /* Port1 mappings */ + { "Port1_1 Mixer", "ADCL Switch", "ADC Left" }, + { "Port1_1 Mixer", "ADCR Switch", "ADC Right" }, + { "Port1_1 Mixer", "DMIC1L Switch", "DMIC1 Left" }, + { "Port1_1 Mixer", "DMIC1R Switch", "DMIC1 Right" }, + { "Port1_1 Mixer", "DMIC2L Switch", "DMIC2 Left" }, + { "Port1_1 Mixer", "DMIC2R Switch", "DMIC2 Right" }, + + { "Port1_2 Mixer", "ADCL Switch", "ADC Left" }, + { "Port1_2 Mixer", "ADCR Switch", "ADC Right" }, + { "Port1_2 Mixer", "DMIC1L Switch", "DMIC1 Left" }, + { "Port1_2 Mixer", "DMIC1R Switch", "DMIC1 Right" }, + { "Port1_2 Mixer", "DMIC2L Switch", "DMIC2 Left" }, + { "Port1_2 Mixer", "DMIC2R Switch", "DMIC2 Right" }, + + { "Port1_3 Mixer", "ADCL Switch", "ADC Left" }, + { "Port1_3 Mixer", "ADCR Switch", "ADC Right" }, + { "Port1_3 Mixer", "DMIC1L Switch", "DMIC1 Left" }, + { "Port1_3 Mixer", "DMIC1R Switch", "DMIC1 Right" }, + { "Port1_3 Mixer", "DMIC2L Switch", "DMIC2 Left" }, + { "Port1_3 Mixer", "DMIC2R Switch", "DMIC2 Right" }, + + { "Port1_4 Mixer", "ADCL Switch", "ADC Left" }, + { "Port1_4 Mixer", "ADCR Switch", "ADC Right" }, + { "Port1_4 Mixer", "DMIC1L Switch", "DMIC1 Left" }, + { "Port1_4 Mixer", "DMIC1R Switch", "DMIC1 Right" }, + { "Port1_4 Mixer", "DMIC2L Switch", "DMIC2 Left" }, + { "Port1_4 Mixer", "DMIC2R Switch", "DMIC2 Right" }, + + { "Port1_5 Mixer", "ADCL Switch", "ADC Left" }, + { "Port1_5 Mixer", "ADCR Switch", "ADC Right" }, + { "Port1_5 Mixer", "DMIC1L Switch", "DMIC1 Left" }, + { "Port1_5 Mixer", "DMIC1R Switch", "DMIC1 Right" }, + { "Port1_5 Mixer", "DMIC2L Switch", "DMIC2 Left" }, + { "Port1_5 Mixer", "DMIC2R Switch", "DMIC2 Right" }, + + { "Port1_6 Mixer", "ADCL Switch", "ADC Left" }, + { "Port1_6 Mixer", "ADCR Switch", "ADC Right" }, + { "Port1_6 Mixer", "DMIC1L Switch", "DMIC1 Left" }, + { "Port1_6 Mixer", "DMIC1R Switch", "DMIC1 Right" }, + { "Port1_6 Mixer", "DMIC2L Switch", "DMIC2 Left" }, + { "Port1_6 Mixer", "DMIC2R Switch", "DMIC2 Right" }, + + { "Port1_7 Mixer", "ADCL Switch", "ADC Left" }, + { "Port1_7 Mixer", "ADCR Switch", "ADC Right" }, + { "Port1_7 Mixer", "DMIC1L Switch", "DMIC1 Left" }, + { "Port1_7 Mixer", "DMIC1R Switch", "DMIC1 Right" }, + { "Port1_7 Mixer", "DMIC2L Switch", "DMIC2 Left" }, + { "Port1_7 Mixer", "DMIC2R Switch", "DMIC2 Right" }, + + { "Port1_8 Mixer", "ADCL Switch", "ADC Left" }, + { "Port1_8 Mixer", "ADCR Switch", "ADC Right" }, + { "Port1_8 Mixer", "DMIC1L Switch", "DMIC1 Left" }, + { "Port1_8 Mixer", "DMIC1R Switch", "DMIC1 Right" }, + { "Port1_8 Mixer", "DMIC2L Switch", "DMIC2 Left" }, + { "Port1_8 Mixer", "DMIC2R Switch", "DMIC2 Right" }, + + { "Port2_1 Mixer", "ADCL Switch", "ADC Left" }, + { "Port2_1 Mixer", "ADCR Switch", "ADC Right" }, + { "Port2_1 Mixer", "DMIC1L Switch", "DMIC1 Left" }, + { "Port2_1 Mixer", "DMIC1R Switch", "DMIC1 Right" }, + { "Port2_1 Mixer", "DMIC2L Switch", "DMIC2 Left" }, + { "Port2_1 Mixer", "DMIC2R Switch", "DMIC2 Right" }, + + { "Port2_2 Mixer", "ADCL Switch", "ADC Left" }, + { "Port2_2 Mixer", "ADCR Switch", "ADC Right" }, + { "Port2_2 Mixer", "DMIC1L Switch", "DMIC1 Left" }, + { "Port2_2 Mixer", "DMIC1R Switch", "DMIC1 Right" }, + { "Port2_2 Mixer", "DMIC2L Switch", "DMIC2 Left" }, + { "Port2_2 Mixer", "DMIC2R Switch", "DMIC2 Right" }, + + { "P1_1_TX", NULL, "Port1_1 Mixer" }, + { "P1_2_TX", NULL, "Port1_2 Mixer" }, + { "P1_3_TX", NULL, "Port1_3 Mixer" }, + { "P1_4_TX", NULL, "Port1_4 Mixer" }, + { "P1_5_TX", NULL, "Port1_5 Mixer" }, + { "P1_6_TX", NULL, "Port1_6 Mixer" }, + { "P1_7_TX", NULL, "Port1_7 Mixer" }, + { "P1_8_TX", NULL, "Port1_8 Mixer" }, + + { "P2_1_TX", NULL, "Port2_1 Mixer" }, + { "P2_2_TX", NULL, "Port2_2 Mixer" }, + + { "PORT1_SDO", "Port1 Capture Switch", "P1_1_TX"}, + { "PORT1_SDO", "Port1 Capture Switch", "P1_2_TX"}, + { "PORT1_SDO", "Port1 Capture Switch", "P1_3_TX"}, + { "PORT1_SDO", "Port1 Capture Switch", "P1_4_TX"}, + { "PORT1_SDO", "Port1 Capture Switch", "P1_5_TX"}, + { "PORT1_SDO", "Port1 Capture Switch", "P1_6_TX"}, + { "PORT1_SDO", "Port1 Capture Switch", "P1_7_TX"}, + { "PORT1_SDO", "Port1 Capture Switch", "P1_8_TX"}, + + { "PORT2_SDO", "Port2 Capture Switch", "P2_1_TX"}, + { "PORT2_SDO", "Port2 Capture Switch", "P2_2_TX"}, + + { "Mic1 Input", NULL, "AMIC1" }, + { "Mic2 Input", NULL, "AMIC2" }, + + { "AUXL Input", NULL, "AUXL" }, + { "AUXR Input", NULL, "AUXR" }, + + /* AUX connections */ + { "ADCL Mux", "Aux_L", "AUXL Input" }, + { "ADCL Mux", "MIC1", "Mic1 Input" }, + + { "ADCR Mux", "Aux_R", "AUXR Input" }, + { "ADCR Mux", "MIC2", "Mic2 Input" }, + + /* ADC connection */ + { "ADC Left", NULL, "ADCL Mux"}, + { "ADC Right", NULL, "ADCR Mux"}, + + { "DMIC1 Left", NULL, "DMIC1DAT"}, + { "DMIC1 Right", NULL, "DMIC1DAT"}, + { "DMIC2 Left", NULL, "DMIC2DAT"}, + { "DMIC2 Right", NULL, "DMIC2DAT"}, + + /* Sidetone map */ + { "Sidetone Mixer", NULL, "ADC Left" }, + { "Sidetone Mixer", NULL, "ADC Right" }, + { "Sidetone Mixer", NULL, "DMIC1 Left" }, + { "Sidetone Mixer", NULL, "DMIC1 Right" }, + { "Sidetone Mixer", NULL, "DMIC2 Left" }, + { "Sidetone Mixer", NULL, "DMIC2 Right" }, + + { "Sidetone", "Sidetone Switch", "Sidetone Mixer" }, +}; + +static int lm49453_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 lm49453_priv *lm49453 = snd_soc_codec_get_drvdata(codec); + u16 clk_div = 0; + + lm49453->fs_rate = params_rate(params); + + /* Setting DAC clock dividers based on substream sample rate. */ + switch (lm49453->fs_rate) { + case 8000: + case 16000: + case 32000: + case 24000: + case 48000: + clk_div = 256; + break; + case 11025: + case 22050: + case 44100: + clk_div = 216; + break; + case 96000: + clk_div = 127; + break; + default: + return -EINVAL; + } + + snd_soc_write(codec, LM49453_P0_ADC_CLK_DIV_REG, clk_div); + snd_soc_write(codec, LM49453_P0_DAC_HP_CLK_DIV_REG, clk_div); + + return 0; +} + +static int lm49453_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + + u16 aif_val; + int mode = 0; + int clk_phase = 0; + int clk_shift = 0; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + aif_val = 0; + break; + case SND_SOC_DAIFMT_CBS_CFM: + aif_val = LM49453_AUDIO_PORT1_BASIC_SYNC_MS; + break; + case SND_SOC_DAIFMT_CBM_CFS: + aif_val = LM49453_AUDIO_PORT1_BASIC_CLK_MS; + break; + case SND_SOC_DAIFMT_CBM_CFM: + aif_val = LM49453_AUDIO_PORT1_BASIC_CLK_MS | + LM49453_AUDIO_PORT1_BASIC_SYNC_MS; + break; + default: + return -EINVAL; + } + + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + break; + case SND_SOC_DAIFMT_DSP_A: + mode = 1; + clk_phase = (1 << 5); + clk_shift = 1; + break; + case SND_SOC_DAIFMT_DSP_B: + mode = 1; + clk_phase = (1 << 5); + clk_shift = 0; + break; + default: + return -EINVAL; + } + + snd_soc_update_bits(codec, LM49453_P0_AUDIO_PORT1_BASIC_REG, + LM49453_AUDIO_PORT1_BASIC_FMT_MASK|BIT(1)|BIT(5), + (aif_val | mode | clk_phase)); + + snd_soc_write(codec, LM49453_P0_AUDIO_PORT1_RX_MSB_REG, clk_shift); + + return 0; +} + +static int lm49453_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id, + unsigned int freq, int dir) +{ + struct snd_soc_codec *codec = dai->codec; + u16 pll_clk = 0; + + switch (freq) { + case 12288000: + case 26000000: + case 19200000: + /* pll clk slection */ + pll_clk = 0; + break; + case 48000: + case 32576: + /* fll clk slection */ + pll_clk = BIT(4); + return 0; + default: + return -EINVAL; + } + + snd_soc_update_bits(codec, LM49453_P0_PMC_SETUP_REG, BIT(4), pll_clk); + + return 0; +} + +static int lm49453_hp_mute(struct snd_soc_dai *dai, int mute) +{ + snd_soc_update_bits(dai->codec, LM49453_P0_DAC_DSP_REG, BIT(1)|BIT(0), + (mute ? (BIT(1)|BIT(0)) : 0)); + return 0; +} + +static int lm49453_lo_mute(struct snd_soc_dai *dai, int mute) +{ + snd_soc_update_bits(dai->codec, LM49453_P0_DAC_DSP_REG, BIT(3)|BIT(2), + (mute ? (BIT(3)|BIT(2)) : 0)); + return 0; +} + +static int lm49453_ls_mute(struct snd_soc_dai *dai, int mute) +{ + snd_soc_update_bits(dai->codec, LM49453_P0_DAC_DSP_REG, BIT(5)|BIT(4), + (mute ? (BIT(5)|BIT(4)) : 0)); + return 0; +} + +static int lm49453_ep_mute(struct snd_soc_dai *dai, int mute) +{ + snd_soc_update_bits(dai->codec, LM49453_P0_DAC_DSP_REG, BIT(4), + (mute ? BIT(4) : 0)); + return 0; +} + +static int lm49453_ha_mute(struct snd_soc_dai *dai, int mute) +{ + snd_soc_update_bits(dai->codec, LM49453_P0_DAC_DSP_REG, BIT(7)|BIT(6), + (mute ? (BIT(7)|BIT(6)) : 0)); + return 0; +} + +static int lm49453_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + struct lm49453_priv *lm49453 = snd_soc_codec_get_drvdata(codec); + + switch (level) { + case SND_SOC_BIAS_ON: + case SND_SOC_BIAS_PREPARE: + break; + + case SND_SOC_BIAS_STANDBY: + if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) + regcache_sync(lm49453->regmap); + + snd_soc_update_bits(codec, LM49453_P0_PMC_SETUP_REG, + LM49453_PMC_SETUP_CHIP_EN, LM49453_CHIP_EN); + break; + + case SND_SOC_BIAS_OFF: + snd_soc_update_bits(codec, LM49453_P0_PMC_SETUP_REG, + LM49453_PMC_SETUP_CHIP_EN, 0); + break; + } + + codec->dapm.bias_level = level; + + return 0; +} + +/* Formates supported by LM49453 driver. */ +#define LM49453_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + +static struct snd_soc_dai_ops lm49453_headset_dai_ops = { + .hw_params = lm49453_hw_params, + .set_sysclk = lm49453_set_dai_sysclk, + .set_fmt = lm49453_set_dai_fmt, + .digital_mute = lm49453_hp_mute, +}; + +static struct snd_soc_dai_ops lm49453_speaker_dai_ops = { + .hw_params = lm49453_hw_params, + .set_sysclk = lm49453_set_dai_sysclk, + .set_fmt = lm49453_set_dai_fmt, + .digital_mute = lm49453_ls_mute, +}; + +static struct snd_soc_dai_ops lm49453_haptic_dai_ops = { + .hw_params = lm49453_hw_params, + .set_sysclk = lm49453_set_dai_sysclk, + .set_fmt = lm49453_set_dai_fmt, + .digital_mute = lm49453_ha_mute, +}; + +static struct snd_soc_dai_ops lm49453_ep_dai_ops = { + .hw_params = lm49453_hw_params, + .set_sysclk = lm49453_set_dai_sysclk, + .set_fmt = lm49453_set_dai_fmt, + .digital_mute = lm49453_ep_mute, +}; + +static struct snd_soc_dai_ops lm49453_lineout_dai_ops = { + .hw_params = lm49453_hw_params, + .set_sysclk = lm49453_set_dai_sysclk, + .set_fmt = lm49453_set_dai_fmt, + .digital_mute = lm49453_lo_mute, +}; + +/* LM49453 dai structure. */ +static const struct snd_soc_dai_driver lm49453_dai[] = { + { + .name = "LM49453 Headset", + .playback = { + .stream_name = "Headset", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = LM49453_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 5, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = LM49453_FORMATS, + }, + .ops = &lm49453_headset_dai_ops, + .symmetric_rates = 1, + }, + { + .name = "LM49453 Speaker", + .playback = { + .stream_name = "Speaker", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = LM49453_FORMATS, + }, + .ops = &lm49453_speaker_dai_ops, + }, + { + .name = "LM49453 Haptic", + .playback = { + .stream_name = "Haptic", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = LM49453_FORMATS, + }, + .ops = &lm49453_haptic_dai_ops, + }, + { + .name = "LM49453 Earpiece", + .playback = { + .stream_name = "Earpiece", + .channels_min = 1, + .channels_max = 1, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = LM49453_FORMATS, + }, + .ops = &lm49453_ep_dai_ops, + }, + { + .name = "LM49453 line out", + .playback = { + .stream_name = "Lineout", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = LM49453_FORMATS, + }, + .ops = &lm49453_lineout_dai_ops, + }, +}; + +static int lm49453_suspend(struct snd_soc_codec *codec) +{ + lm49453_set_bias_level(codec, SND_SOC_BIAS_OFF); + return 0; +} + +static int lm49453_resume(struct snd_soc_codec *codec) +{ + lm49453_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + return 0; +} + +static int lm49453_probe(struct snd_soc_codec *codec) +{ + struct lm49453_priv *lm49453 = snd_soc_codec_get_drvdata(codec); + int ret = 0; + + codec->control_data = lm49453->regmap; + + ret = snd_soc_codec_set_cache_io(codec, 8, 8, SND_SOC_REGMAP); + if (ret < 0) { + dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret); + return ret; + } + + return 0; +} + +/* 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 = { + .probe = lm49453_probe, + .remove = lm49453_remove, + .suspend = lm49453_suspend, + .resume = lm49453_resume, + .set_bias_level = lm49453_set_bias_level, + .controls = lm49453_snd_controls, + .num_controls = ARRAY_SIZE(lm49453_snd_controls), + .dapm_widgets = lm49453_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(lm49453_dapm_widgets), + .dapm_routes = lm49453_audio_map, + .num_dapm_routes = ARRAY_SIZE(lm49453_audio_map), + .idle_bias_off = true, +}; + +static const struct regmap_config lm49453_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = LM49453_MAX_REGISTER, + .reg_defaults = lm49453_reg_defs, + .num_reg_defaults = ARRAY_SIZE(lm49453_reg_defs), + .cache_type = REGCACHE_RBTREE, +}; + +static __devinit int lm49453_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct lm49453_priv *lm49453; + int ret = 0; + + lm49453 = devm_kzalloc(&i2c->dev, sizeof(struct lm49453_priv), + GFP_KERNEL); + + if (lm49453 == NULL) + return -ENOMEM; + + i2c_set_clientdata(i2c, lm49453); + + lm49453->regmap = regmap_init_i2c(i2c, &lm49453_regmap_config); + if (IS_ERR(lm49453->regmap)) { + ret = PTR_ERR(lm49453->regmap); + dev_err(&i2c->dev, "Failed to allocate register map: %d\n", + ret); + return ret; + } + + ret = snd_soc_register_codec(&i2c->dev, + &soc_codec_dev_lm49453, + lm49453_dai, ARRAY_SIZE(lm49453_dai)); + if (ret < 0) { + dev_err(&i2c->dev, "Failed to register codec: %d\n", ret); + regmap_exit(lm49453->regmap); + return ret; + } + + return ret; +} + +static int __devexit lm49453_i2c_remove(struct i2c_client *client) +{ + struct lm49453_priv *lm49453 = i2c_get_clientdata(client); + + snd_soc_unregister_codec(&client->dev); + regmap_exit(lm49453->regmap); + return 0; +} + +static const struct i2c_device_id lm49453_i2c_id[] = { + { "lm49453", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, lm49453_i2c_id); + +static struct i2c_driver lm49453_i2c_driver = { + .driver = { + .name = "lm49453", + .owner = THIS_MODULE, + }, + .probe = lm49453_i2c_probe, + .remove = __devexit_p(lm49453_i2c_remove), + .id_table = lm49453_i2c_id, +}; + +module_i2c_driver(lm49453_i2c_driver); + +MODULE_DESCRIPTION("ASoC LM49453 driver"); +MODULE_AUTHOR("M R Swami Reddy <MR.Swami.Reddy@ti.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/lm49453.h b/sound/soc/codecs/lm49453.h new file mode 100644 index 00000000000..a63cfa5c088 --- /dev/null +++ b/sound/soc/codecs/lm49453.h @@ -0,0 +1,380 @@ +/* + * lm49453.h - LM49453 ALSA Soc Audio drive + * + * Copyright (c) 2012 Texas Instruments, Inc + * + * 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. + * + */ + +#ifndef _LM49453_H +#define _LM49453_H + +#include <linux/bitops.h> + +/* LM49453_P0 register space for page0 */ +#define LM49453_P0_PMC_SETUP_REG 0x00 +#define LM49453_P0_PLL_CLK_SEL1_REG 0x01 +#define LM49453_P0_PLL_CLK_SEL2_REG 0x02 +#define LM49453_P0_PMC_CLK_DIV_REG 0x03 +#define LM49453_P0_HSDET_CLK_DIV_REG 0x04 +#define LM49453_P0_DMIC_CLK_DIV_REG 0x05 +#define LM49453_P0_ADC_CLK_DIV_REG 0x06 +#define LM49453_P0_DAC_OT_CLK_DIV_REG 0x07 +#define LM49453_P0_PLL_HF_M_REG 0x08 +#define LM49453_P0_PLL_LF_M_REG 0x09 +#define LM49453_P0_PLL_NL_REG 0x0A +#define LM49453_P0_PLL_N_MODL_REG 0x0B +#define LM49453_P0_PLL_N_MODH_REG 0x0C +#define LM49453_P0_PLL_P1_REG 0x0D +#define LM49453_P0_PLL_P2_REG 0x0E +#define LM49453_P0_FLL_REF_FREQL_REG 0x0F +#define LM49453_P0_FLL_REF_FREQH_REG 0x10 +#define LM49453_P0_VCO_TARGETLL_REG 0x11 +#define LM49453_P0_VCO_TARGETLH_REG 0x12 +#define LM49453_P0_VCO_TARGETHL_REG 0x13 +#define LM49453_P0_VCO_TARGETHH_REG 0x14 +#define LM49453_P0_PLL_CONFIG_REG 0x15 +#define LM49453_P0_DAC_CLK_SEL_REG 0x16 +#define LM49453_P0_DAC_HP_CLK_DIV_REG 0x17 + +/* Analog Mixer Input Stages */ +#define LM49453_P0_MICL_REG 0x20 +#define LM49453_P0_MICR_REG 0x21 +#define LM49453_P0_EP_REG 0x24 +#define LM49453_P0_DIS_PKVL_FB_REG 0x25 + +/* Analog Mixer Output Stages */ +#define LM49453_P0_ANALOG_MIXER_ADC_REG 0x2E + +/*ADC or DAC */ +#define LM49453_P0_ADC_DSP_REG 0x30 +#define LM49453_P0_DAC_DSP_REG 0x31 + +/* EFFECTS ENABLES */ +#define LM49453_P0_ADC_FX_ENABLES_REG 0x33 + +/* GPIO */ +#define LM49453_P0_GPIO1_REG 0x38 +#define LM49453_P0_GPIO2_REG 0x39 +#define LM49453_P0_GPIO3_REG 0x3A +#define LM49453_P0_HAP_CTL_REG 0x3B +#define LM49453_P0_HAP_FREQ_PROG_LEFTL_REG 0x3C +#define LM49453_P0_HAP_FREQ_PROG_LEFTH_REG 0x3D +#define LM49453_P0_HAP_FREQ_PROG_RIGHTL_REG 0x3E +#define LM49453_P0_HAP_FREQ_PROG_RIGHTH_REG 0x3F + +/* DIGITAL MIXER */ +#define LM49453_P0_DMIX_CLK_SEL_REG 0x40 +#define LM49453_P0_PORT1_RX_LVL1_REG 0x41 +#define LM49453_P0_PORT1_RX_LVL2_REG 0x42 +#define LM49453_P0_PORT2_RX_LVL_REG 0x43 +#define LM49453_P0_PORT1_TX1_REG 0x44 +#define LM49453_P0_PORT1_TX2_REG 0x45 +#define LM49453_P0_PORT1_TX3_REG 0x46 +#define LM49453_P0_PORT1_TX4_REG 0x47 +#define LM49453_P0_PORT1_TX5_REG 0x48 +#define LM49453_P0_PORT1_TX6_REG 0x49 +#define LM49453_P0_PORT1_TX7_REG 0x4A +#define LM49453_P0_PORT1_TX8_REG 0x4B +#define LM49453_P0_PORT2_TX1_REG 0x4C +#define LM49453_P0_PORT2_TX2_REG 0x4D +#define LM49453_P0_STN_SEL_REG 0x4F +#define LM49453_P0_DACHPL1_REG 0x50 +#define LM49453_P0_DACHPL2_REG 0x51 +#define LM49453_P0_DACHPR1_REG 0x52 +#define LM49453_P0_DACHPR2_REG 0x53 +#define LM49453_P0_DACLOL1_REG 0x54 +#define LM49453_P0_DACLOL2_REG 0x55 +#define LM49453_P0_DACLOR1_REG 0x56 +#define LM49453_P0_DACLOR2_REG 0x57 +#define LM49453_P0_DACLSL1_REG 0x58 +#define LM49453_P0_DACLSL2_REG 0x59 +#define LM49453_P0_DACLSR1_REG 0x5A +#define LM49453_P0_DACLSR2_REG 0x5B +#define LM49453_P0_DACHAL1_REG 0x5C +#define LM49453_P0_DACHAL2_REG 0x5D +#define LM49453_P0_DACHAR1_REG 0x5E +#define LM49453_P0_DACHAR2_REG 0x5F + +/* AUDIO PORT 1 (TDM) */ +#define LM49453_P0_AUDIO_PORT1_BASIC_REG 0x60 +#define LM49453_P0_AUDIO_PORT1_CLK_GEN1_REG 0x61 +#define LM49453_P0_AUDIO_PORT1_CLK_GEN2_REG 0x62 +#define LM49453_P0_AUDIO_PORT1_CLK_GEN3_REG 0x63 +#define LM49453_P0_AUDIO_PORT1_SYNC_RATE_REG 0x64 +#define LM49453_P0_AUDIO_PORT1_SYNC_SDO_SETUP_REG 0x65 +#define LM49453_P0_AUDIO_PORT1_DATA_WIDTH_REG 0x66 +#define LM49453_P0_AUDIO_PORT1_RX_MSB_REG 0x67 +#define LM49453_P0_AUDIO_PORT1_TX_MSB_REG 0x68 +#define LM49453_P0_AUDIO_PORT1_TDM_CHANNELS_REG 0x69 + +/* AUDIO PORT 2 */ +#define LM49453_P0_AUDIO_PORT2_BASIC_REG 0x6A +#define LM49453_P0_AUDIO_PORT2_CLK_GEN1_REG 0x6B +#define LM49453_P0_AUDIO_PORT2_CLK_GEN2_REG 0x6C +#define LM49453_P0_AUDIO_PORT2_SYNC_GEN_REG 0x6D +#define LM49453_P0_AUDIO_PORT2_DATA_WIDTH_REG 0x6E +#define LM49453_P0_AUDIO_PORT2_RX_MODE_REG 0x6F +#define LM49453_P0_AUDIO_PORT2_TX_MODE_REG 0x70 + +/* SAMPLE RATE */ +#define LM49453_P0_PORT1_SR_LSB_REG 0x79 +#define LM49453_P0_PORT1_SR_MSB_REG 0x7A +#define LM49453_P0_PORT2_SR_LSB_REG 0x7B +#define LM49453_P0_PORT2_SR_MSB_REG 0x7C + +/* EFFECTS - HPFs */ +#define LM49453_P0_HPF_REG 0x80 + +/* EFFECTS ADC ALC */ +#define LM49453_P0_ADC_ALC1_REG 0x82 +#define LM49453_P0_ADC_ALC2_REG 0x83 +#define LM49453_P0_ADC_ALC3_REG 0x84 +#define LM49453_P0_ADC_ALC4_REG 0x85 +#define LM49453_P0_ADC_ALC5_REG 0x86 +#define LM49453_P0_ADC_ALC6_REG 0x87 +#define LM49453_P0_ADC_ALC7_REG 0x88 +#define LM49453_P0_ADC_ALC8_REG 0x89 +#define LM49453_P0_DMIC1_LEVELL_REG 0x8A +#define LM49453_P0_DMIC1_LEVELR_REG 0x8B +#define LM49453_P0_DMIC2_LEVELL_REG 0x8C +#define LM49453_P0_DMIC2_LEVELR_REG 0x8D +#define LM49453_P0_ADC_LEVELL_REG 0x8E +#define LM49453_P0_ADC_LEVELR_REG 0x8F +#define LM49453_P0_DAC_HP_LEVELL_REG 0x90 +#define LM49453_P0_DAC_HP_LEVELR_REG 0x91 +#define LM49453_P0_DAC_LO_LEVELL_REG 0x92 +#define LM49453_P0_DAC_LO_LEVELR_REG 0x93 +#define LM49453_P0_DAC_LS_LEVELL_REG 0x94 +#define LM49453_P0_DAC_LS_LEVELR_REG 0x95 +#define LM49453_P0_DAC_HA_LEVELL_REG 0x96 +#define LM49453_P0_DAC_HA_LEVELR_REG 0x97 +#define LM49453_P0_SOFT_MUTE_REG 0x98 +#define LM49453_P0_DMIC_MUTE_CFG_REG 0x99 +#define LM49453_P0_ADC_MUTE_CFG_REG 0x9A +#define LM49453_P0_DAC_MUTE_CFG_REG 0x9B + +/*DIGITAL MIC1 */ +#define LM49453_P0_DIGITAL_MIC1_CONFIG_REG 0xB0 +#define LM49453_P0_DIGITAL_MIC1_DATA_DELAYL_REG 0xB1 +#define LM49453_P0_DIGITAL_MIC1_DATA_DELAYR_REG 0xB2 + +/*DIGITAL MIC2 */ +#define LM49453_P0_DIGITAL_MIC2_CONFIG_REG 0xB3 +#define LM49453_P0_DIGITAL_MIC2_DATA_DELAYL_REG 0xB4 +#define LM49453_P0_DIGITAL_MIC2_DATA_DELAYR_REG 0xB5 + +/* ADC DECIMATOR */ +#define LM49453_P0_ADC_DECIMATOR_REG 0xB6 + +/* DAC CONFIGURE */ +#define LM49453_P0_DAC_CONFIG_REG 0xB7 + +/* SIDETONE */ +#define LM49453_P0_STN_VOL_ADCL_REG 0xB8 +#define LM49453_P0_STN_VOL_ADCR_REG 0xB9 +#define LM49453_P0_STN_VOL_DMIC1L_REG 0xBA +#define LM49453_P0_STN_VOL_DMIC1R_REG 0xBB +#define LM49453_P0_STN_VOL_DMIC2L_REG 0xBC +#define LM49453_P0_STN_VOL_DMIC2R_REG 0xBD + +/* ADC/DAC CLIPPING MONITORS (Read Only/Write to Clear) */ +#define LM49453_P0_ADC_DEC_CLIP_REG 0xC2 +#define LM49453_P0_ADC_HPF_CLIP_REG 0xC3 +#define LM49453_P0_ADC_LVL_CLIP_REG 0xC4 +#define LM49453_P0_DAC_LVL_CLIP_REG 0xC5 + +/* ADC ALC EFFECT MONITORS (Read Only) */ +#define LM49453_P0_ADC_LVLMONL_REG 0xC8 +#define LM49453_P0_ADC_LVLMONR_REG 0xC9 +#define LM49453_P0_ADC_ALCMONL_REG 0xCA +#define LM49453_P0_ADC_ALCMONR_REG 0xCB +#define LM49453_P0_ADC_MUTED_REG 0xCC +#define LM49453_P0_DAC_MUTED_REG 0xCD + +/* HEADSET DETECT */ +#define LM49453_P0_HSD_PPB_LONG_CNT_LIMITL_REG 0xD0 +#define LM49453_P0_HSD_PPB_LONG_CNT_LIMITR_REG 0xD1 +#define LM49453_P0_HSD_PIN3_4_EX_LOOP_CNT_LIMITL_REG 0xD2 +#define LM49453_P0_HSD_PIN3_4_EX_LOOP_CNT_LIMITH_REG 0xD3 +#define LM49453_P0_HSD_TIMEOUT1_REG 0xD4 +#define LM49453_P0_HSD_TIMEOUT2_REG 0xD5 +#define LM49453_P0_HSD_TIMEOUT3_REG 0xD6 +#define LM49453_P0_HSD_PIN3_4_CFG_REG 0xD7 +#define LM49453_P0_HSD_IRQ1_REG 0xD8 +#define LM49453_P0_HSD_IRQ2_REG 0xD9 +#define LM49453_P0_HSD_IRQ3_REG 0xDA +#define LM49453_P0_HSD_IRQ4_REG 0xDB +#define LM49453_P0_HSD_IRQ_MASK1_REG 0xDC +#define LM49453_P0_HSD_IRQ_MASK2_REG 0xDD +#define LM49453_P0_HSD_IRQ_MASK3_REG 0xDE +#define LM49453_P0_HSD_R_HPLL_REG 0xE0 +#define LM49453_P0_HSD_R_HPLH_REG 0xE1 +#define LM49453_P0_HSD_R_HPLU_REG 0xE2 +#define LM49453_P0_HSD_R_HPRL_REG 0xE3 +#define LM49453_P0_HSD_R_HPRH_REG 0xE4 +#define LM49453_P0_HSD_R_HPRU_REG 0xE5 +#define LM49453_P0_HSD_VEL_L_FINALL_REG 0xE6 +#define LM49453_P0_HSD_VEL_L_FINALH_REG 0xE7 +#define LM49453_P0_HSD_VEL_L_FINALU_REG 0xE8 +#define LM49453_P0_HSD_RO_FINALL_REG 0xE9 +#define LM49453_P0_HSD_RO_FINALH_REG 0xEA +#define LM49453_P0_HSD_RO_FINALU_REG 0xEB +#define LM49453_P0_HSD_VMIC_BIAS_FINALL_REG 0xEC +#define LM49453_P0_HSD_VMIC_BIAS_FINALH_REG 0xED +#define LM49453_P0_HSD_VMIC_BIAS_FINALU_REG 0xEE +#define LM49453_P0_HSD_PIN_CONFIG_REG 0xEF +#define LM49453_P0_HSD_PLUG_DETECT_BB_IRQ_STATUS1_REG 0xF1 +#define LM49453_P0_HSD_PLUG_DETECT_BB_IRQ_STATUS2_REG 0xF2 +#define LM49453_P0_HSD_PLUG_DETECT_BB_IRQ_STATUS3_REG 0xF3 +#define LM49453_P0_HSD_PLUG_DETECT_BB_IRQ_STATEL_REG 0xF4 +#define LM49453_P0_HSD_PLUG_DETECT_BB_IRQ_STATEH_REG 0xF5 + +/* I/O PULLDOWN CONFIG */ +#define LM49453_P0_PULL_CONFIG1_REG 0xF8 +#define LM49453_P0_PULL_CONFIG2_REG 0xF9 +#define LM49453_P0_PULL_CONFIG3_REG 0xFA + +/* RESET */ +#define LM49453_P0_RESET_REG 0xFE + +/* PAGE */ +#define LM49453_PAGE_REG 0xFF + +#define LM49453_MAX_REGISTER (0xFF+1) + +/* LM49453_P0_PMC_SETUP_REG (0x00h) */ +#define LM49453_PMC_SETUP_CHIP_EN (BIT(1)|BIT(0)) +#define LM49453_PMC_SETUP_PLL_EN BIT(2) +#define LM49453_PMC_SETUP_PLL_P2_EN BIT(3) +#define LM49453_PMC_SETUP_PLL_FLL BIT(4) +#define LM49453_PMC_SETUP_MCLK_OVER BIT(5) +#define LM49453_PMC_SETUP_RTC_CLK_OVER BIT(6) +#define LM49453_PMC_SETUP_CHIP_ACTIVE BIT(7) + +/* Chip Enable bits */ +#define LM49453_CHIP_EN_SHUTDOWN 0x00 +#define LM49453_CHIP_EN 0x01 +#define LM49453_CHIP_EN_HSD_DETECT 0x02 +#define LM49453_CHIP_EN_INVALID_HSD 0x03 + +/* LM49453_P0_PLL_CLK_SEL1_REG (0x01h) */ +#define LM49453_CLK_SEL1_MCLK_SEL 0x11 +#define LM49453_CLK_SEL1_RTC_SEL 0x11 +#define LM49453_CLK_SEL1_PORT1_SEL 0x10 +#define LM49453_CLK_SEL1_PORT2_SEL 0x11 + +/* LM49453_P0_PLL_CLK_SEL2_REG (0x02h) */ +#define LM49453_CLK_SEL2_ADC_CLK_SEL 0x38 + +/* LM49453_P0_FLL_REF_FREQL_REG (0x0F) */ +#define LM49453_FLL_REF_FREQ_VAL 0x8ca0001 + +/* LM49453_P0_VCO_TARGETLL_REG (0x11) */ +#define LM49453_VCO_TARGET_VAL 0x8ca0001 + +/* LM49453_P0_ADC_DSP_REG (0x30h) */ +#define LM49453_ADC_DSP_ADC_MUTEL BIT(0) +#define LM49453_ADC_DSP_ADC_MUTER BIT(1) +#define LM49453_ADC_DSP_DMIC1_MUTEL BIT(2) +#define LM49453_ADC_DSP_DMIC1_MUTER BIT(3) +#define LM49453_ADC_DSP_DMIC2_MUTEL BIT(4) +#define LM49453_ADC_DSP_DMIC2_MUTER BIT(5) +#define LM49453_ADC_DSP_MUTE_ALL 0x3F + +/* LM49453_P0_DAC_DSP_REG (0x31h) */ +#define LM49453_DAC_DSP_MUTE_ALL 0xFF + +/* LM49453_P0_AUDIO_PORT1_BASIC_REG (0x60h) */ +#define LM49453_AUDIO_PORT1_BASIC_FMT_MASK (BIT(4)|BIT(3)) +#define LM49453_AUDIO_PORT1_BASIC_CLK_MS BIT(3) +#define LM49453_AUDIO_PORT1_BASIC_SYNC_MS BIT(4) + +/* LM49453_P0_RESET_REG (0xFEh) */ +#define LM49453_RESET_REG_RST BIT(0) + +/* Page select register bits (0xFF) */ +#define LM49453_PAGE0_SELECT 0x0 +#define LM49453_PAGE1_SELECT 0x1 + +/* LM49453_P0_HSD_PIN3_4_CFG_REG (Jack Pin config - 0xD7) */ +#define LM49453_JACK_DISABLE 0x00 +#define LM49453_JACK_CONFIG1 0x01 +#define LM49453_JACK_CONFIG2 0x02 +#define LM49453_JACK_CONFIG3 0x03 +#define LM49453_JACK_CONFIG4 0x04 +#define LM49453_JACK_CONFIG5 0x05 + +/* Page 1 REGISTERS */ + +/* SIDETONE */ +#define LM49453_P1_SIDETONE_SA0L_REG 0x80 +#define LM49453_P1_SIDETONE_SA0H_REG 0x81 +#define LM49453_P1_SIDETONE_SAB0U_REG 0x82 +#define LM49453_P1_SIDETONE_SB0L_REG 0x83 +#define LM49453_P1_SIDETONE_SB0H_REG 0x84 +#define LM49453_P1_SIDETONE_SH0L_REG 0x85 +#define LM49453_P1_SIDETONE_SH0H_REG 0x86 +#define LM49453_P1_SIDETONE_SH0U_REG 0x87 +#define LM49453_P1_SIDETONE_SA1L_REG 0x88 +#define LM49453_P1_SIDETONE_SA1H_REG 0x89 +#define LM49453_P1_SIDETONE_SAB1U_REG 0x8A +#define LM49453_P1_SIDETONE_SB1L_REG 0x8B +#define LM49453_P1_SIDETONE_SB1H_REG 0x8C +#define LM49453_P1_SIDETONE_SH1L_REG 0x8D +#define LM49453_P1_SIDETONE_SH1H_REG 0x8E +#define LM49453_P1_SIDETONE_SH1U_REG 0x8F +#define LM49453_P1_SIDETONE_SA2L_REG 0x90 +#define LM49453_P1_SIDETONE_SA2H_REG 0x91 +#define LM49453_P1_SIDETONE_SAB2U_REG 0x92 +#define LM49453_P1_SIDETONE_SB2L_REG 0x93 +#define LM49453_P1_SIDETONE_SB2H_REG 0x94 +#define LM49453_P1_SIDETONE_SH2L_REG 0x95 +#define LM49453_P1_SIDETONE_SH2H_REG 0x96 +#define LM49453_P1_SIDETONE_SH2U_REG 0x97 +#define LM49453_P1_SIDETONE_SA3L_REG 0x98 +#define LM49453_P1_SIDETONE_SA3H_REG 0x99 +#define LM49453_P1_SIDETONE_SAB3U_REG 0x9A +#define LM49453_P1_SIDETONE_SB3L_REG 0x9B +#define LM49453_P1_SIDETONE_SB3H_REG 0x9C +#define LM49453_P1_SIDETONE_SH3L_REG 0x9D +#define LM49453_P1_SIDETONE_SH3H_REG 0x9E +#define LM49453_P1_SIDETONE_SH3U_REG 0x9F +#define LM49453_P1_SIDETONE_SA4L_REG 0xA0 +#define LM49453_P1_SIDETONE_SA4H_REG 0xA1 +#define LM49453_P1_SIDETONE_SAB4U_REG 0xA2 +#define LM49453_P1_SIDETONE_SB4L_REG 0xA3 +#define LM49453_P1_SIDETONE_SB4H_REG 0xA4 +#define LM49453_P1_SIDETONE_SH4L_REG 0xA5 +#define LM49453_P1_SIDETONE_SH4H_REG 0xA6 +#define LM49453_P1_SIDETONE_SH4U_REG 0xA7 +#define LM49453_P1_SIDETONE_SA5L_REG 0xA8 +#define LM49453_P1_SIDETONE_SA5H_REG 0xA9 +#define LM49453_P1_SIDETONE_SAB5U_REG 0xAA +#define LM49453_P1_SIDETONE_SB5L_REG 0xAB +#define LM49453_P1_SIDETONE_SB5H_REG 0xAC +#define LM49453_P1_SIDETONE_SH5L_REG 0xAD +#define LM49453_P1_SIDETONE_SH5H_REG 0xAE +#define LM49453_P1_SIDETONE_SH5U_REG 0xAF + +/* CHARGE PUMP CONFIG */ +#define LM49453_P1_CP_CONFIG1_REG 0xB0 +#define LM49453_P1_CP_CONFIG2_REG 0xB1 +#define LM49453_P1_CP_CONFIG3_REG 0xB2 +#define LM49453_P1_CP_CONFIG4_REG 0xB3 +#define LM49453_P1_CP_LA_VTH1L_REG 0xB4 +#define LM49453_P1_CP_LA_VTH1M_REG 0xB5 +#define LM49453_P1_CP_LA_VTH2L_REG 0xB6 +#define LM49453_P1_CP_LA_VTH2M_REG 0xB7 +#define LM49453_P1_CP_LA_VTH3L_REG 0xB8 +#define LM49453_P1_CP_LA_VTH3H_REG 0xB9 +#define LM49453_P1_CP_CLK_DIV_REG 0xBA + +/* DAC */ +#define LM49453_P1_DAC_CHOP_REG 0xC0 + +#define LM49453_CLK_SRC_MCLK 1 +#endif diff --git a/sound/soc/codecs/max98095.c b/sound/soc/codecs/max98095.c index 0bb511a0388..35179e2c23c 100644 --- a/sound/soc/codecs/max98095.c +++ b/sound/soc/codecs/max98095.c @@ -24,6 +24,7 @@ #include <linux/slab.h> #include <asm/div64.h> #include <sound/max98095.h> +#include <sound/jack.h> #include "max98095.h" enum max98095_type { @@ -51,6 +52,8 @@ struct max98095_priv { u8 lin_state; unsigned int mic1pre; unsigned int mic2pre; + struct snd_soc_jack *headphone_jack; + struct snd_soc_jack *mic_jack; }; static const u8 max98095_reg_def[M98095_REG_CNT] = { @@ -2173,9 +2176,125 @@ static void max98095_handle_pdata(struct snd_soc_codec *codec) max98095_handle_bq_pdata(codec); } +static irqreturn_t max98095_report_jack(int irq, void *data) +{ + struct snd_soc_codec *codec = data; + struct max98095_priv *max98095 = snd_soc_codec_get_drvdata(codec); + unsigned int value; + int hp_report = 0; + int mic_report = 0; + + /* Read the Jack Status Register */ + value = snd_soc_read(codec, M98095_007_JACK_AUTO_STS); + + /* If ddone is not set, then detection isn't finished yet */ + if ((value & M98095_DDONE) == 0) + return IRQ_NONE; + + /* if hp, check its bit, and if set, clear it */ + if ((value & M98095_HP_IN || value & M98095_LO_IN) && + max98095->headphone_jack) + hp_report |= SND_JACK_HEADPHONE; + + /* if mic, check its bit, and if set, clear it */ + if ((value & M98095_MIC_IN) && max98095->mic_jack) + mic_report |= SND_JACK_MICROPHONE; + + if (max98095->headphone_jack == max98095->mic_jack) { + snd_soc_jack_report(max98095->headphone_jack, + hp_report | mic_report, + SND_JACK_HEADSET); + } else { + if (max98095->headphone_jack) + snd_soc_jack_report(max98095->headphone_jack, + hp_report, SND_JACK_HEADPHONE); + if (max98095->mic_jack) + snd_soc_jack_report(max98095->mic_jack, + mic_report, SND_JACK_MICROPHONE); + } + + return IRQ_HANDLED; +} + +int max98095_jack_detect_enable(struct snd_soc_codec *codec) +{ + struct max98095_priv *max98095 = snd_soc_codec_get_drvdata(codec); + int ret = 0; + int detect_enable = M98095_JDEN; + unsigned int slew = M98095_DEFAULT_SLEW_DELAY; + + if (max98095->pdata->jack_detect_pin5en) + detect_enable |= M98095_PIN5EN; + + if (max98095->pdata->jack_detect_delay) + slew = max98095->pdata->jack_detect_delay; + + ret = snd_soc_write(codec, M98095_08E_JACK_DC_SLEW, slew); + if (ret < 0) { + dev_err(codec->dev, "Failed to cfg auto detect %d\n", ret); + return ret; + } + + /* configure auto detection to be enabled */ + ret = snd_soc_write(codec, M98095_089_JACK_DET_AUTO, detect_enable); + if (ret < 0) { + dev_err(codec->dev, "Failed to cfg auto detect %d\n", ret); + return ret; + } + + return ret; +} + +int max98095_jack_detect_disable(struct snd_soc_codec *codec) +{ + int ret = 0; + + /* configure auto detection to be disabled */ + ret = snd_soc_write(codec, M98095_089_JACK_DET_AUTO, 0x0); + if (ret < 0) { + dev_err(codec->dev, "Failed to cfg auto detect %d\n", ret); + return ret; + } + + return ret; +} + +int max98095_jack_detect(struct snd_soc_codec *codec, + struct snd_soc_jack *hp_jack, struct snd_soc_jack *mic_jack) +{ + struct max98095_priv *max98095 = snd_soc_codec_get_drvdata(codec); + struct i2c_client *client = to_i2c_client(codec->dev); + int ret = 0; + + max98095->headphone_jack = hp_jack; + max98095->mic_jack = mic_jack; + + /* only progress if we have at least 1 jack pointer */ + if (!hp_jack && !mic_jack) + return -EINVAL; + + max98095_jack_detect_enable(codec); + + /* enable interrupts for headphone jack detection */ + ret = snd_soc_update_bits(codec, M98095_013_JACK_INT_EN, + M98095_IDDONE, M98095_IDDONE); + if (ret < 0) { + dev_err(codec->dev, "Failed to cfg jack irqs %d\n", ret); + return ret; + } + + max98095_report_jack(client->irq, codec); + return 0; +} + #ifdef CONFIG_PM static int max98095_suspend(struct snd_soc_codec *codec) { + struct max98095_priv *max98095 = snd_soc_codec_get_drvdata(codec); + + if (max98095->headphone_jack || max98095->mic_jack) + max98095_jack_detect_disable(codec); + max98095_set_bias_level(codec, SND_SOC_BIAS_OFF); return 0; @@ -2183,8 +2302,16 @@ static int max98095_suspend(struct snd_soc_codec *codec) static int max98095_resume(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_STANDBY); + if (max98095->headphone_jack || max98095->mic_jack) { + max98095_jack_detect_enable(codec); + max98095_report_jack(client->irq, codec); + } + return 0; } #else @@ -2227,6 +2354,7 @@ static int max98095_probe(struct snd_soc_codec *codec) { struct max98095_priv *max98095 = snd_soc_codec_get_drvdata(codec); struct max98095_cdata *cdata; + struct i2c_client *client; int ret = 0; ret = snd_soc_codec_set_cache_io(codec, 8, 8, SND_SOC_I2C); @@ -2238,6 +2366,8 @@ static int max98095_probe(struct snd_soc_codec *codec) /* reset the codec, the DSP core, and disable all interrupts */ max98095_reset(codec); + client = to_i2c_client(codec->dev); + /* initialize private data */ max98095->sysclk = (unsigned)-1; @@ -2266,11 +2396,23 @@ static int max98095_probe(struct snd_soc_codec *codec) max98095->mic1pre = 0; max98095->mic2pre = 0; + if (client->irq) { + /* register an audio interrupt */ + ret = request_threaded_irq(client->irq, NULL, + max98095_report_jack, + IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, + "max98095", codec); + if (ret) { + dev_err(codec->dev, "Failed to request IRQ: %d\n", ret); + goto err_access; + } + } + ret = snd_soc_read(codec, M98095_0FF_REV_ID); if (ret < 0) { dev_err(codec->dev, "Failure reading hardware revision: %d\n", ret); - goto err_access; + goto err_irq; } dev_info(codec->dev, "Hardware revision: %c\n", ret - 0x40 + 'A'); @@ -2306,14 +2448,28 @@ static int max98095_probe(struct snd_soc_codec *codec) max98095_add_widgets(codec); + return 0; + +err_irq: + if (client->irq) + free_irq(client->irq, codec); err_access: return ret; } 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); + + if (client->irq) + free_irq(client->irq, codec); + return 0; } diff --git a/sound/soc/codecs/max98095.h b/sound/soc/codecs/max98095.h index 891584a0eb0..2ebbe4e894b 100644 --- a/sound/soc/codecs/max98095.h +++ b/sound/soc/codecs/max98095.h @@ -175,11 +175,23 @@ /* MAX98095 Registers Bit Fields */ +/* M98095_007_JACK_AUTO_STS */ + #define M98095_MIC_IN (1<<3) + #define M98095_LO_IN (1<<5) + #define M98095_HP_IN (1<<6) + #define M98095_DDONE (1<<7) + /* M98095_00F_HOST_CFG */ #define M98095_SEG (1<<0) #define M98095_XTEN (1<<1) #define M98095_MDLLEN (1<<2) +/* M98095_013_JACK_INT_EN */ + #define M98095_IMIC_IN (1<<3) + #define M98095_ILO_IN (1<<5) + #define M98095_IHP_IN (1<<6) + #define M98095_IDDONE (1<<7) + /* M98095_027_DAI1_CLKMODE, M98095_031_DAI2_CLKMODE, M98095_03B_DAI3_CLKMODE */ #define M98095_CLKMODE_MASK 0xFF @@ -255,6 +267,10 @@ #define M98095_EQ2EN (1<<1) #define M98095_EQ1EN (1<<0) +/* M98095_089_JACK_DET_AUTO */ + #define M98095_PIN5EN (1<<2) + #define M98095_JDEN (1<<7) + /* M98095_090_PWR_EN_IN */ #define M98095_INEN (1<<7) #define M98095_MB2EN (1<<3) @@ -296,4 +312,10 @@ #define M98095_174_DAI1_BQ_BASE 0x74 #define M98095_17E_DAI2_BQ_BASE 0x7E +/* Default Delay used in Slew Rate Calculation for Jack detection */ +#define M98095_DEFAULT_SLEW_DELAY 0x18 + +extern int max98095_jack_detect(struct snd_soc_codec *codec, + struct snd_soc_jack *hp_jack, struct snd_soc_jack *mic_jack); + #endif diff --git a/sound/soc/codecs/mc13783.c b/sound/soc/codecs/mc13783.c new file mode 100644 index 00000000000..6276e352125 --- /dev/null +++ b/sound/soc/codecs/mc13783.c @@ -0,0 +1,786 @@ +/* + * Copyright 2008 Juergen Beisert, kernel@pengutronix.de + * Copyright 2009 Sascha Hauer, s.hauer@pengutronix.de + * Copyright 2012 Philippe Retornaz, philippe.retornaz@epfl.ch + * + * Initial development of this code was funded by + * Phytec Messtechnik GmbH, http://www.phytec.de + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ +#include <linux/module.h> +#include <linux/device.h> +#include <linux/mfd/mc13xxx.h> +#include <linux/slab.h> +#include <sound/core.h> +#include <sound/control.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/initval.h> +#include <sound/soc-dapm.h> + +#include "mc13783.h" + +#define MC13783_AUDIO_RX0 36 +#define MC13783_AUDIO_RX1 37 +#define MC13783_AUDIO_TX 38 +#define MC13783_SSI_NETWORK 39 +#define MC13783_AUDIO_CODEC 40 +#define MC13783_AUDIO_DAC 41 + +#define AUDIO_RX0_ALSPEN (1 << 5) +#define AUDIO_RX0_ALSPSEL (1 << 7) +#define AUDIO_RX0_ADDCDC (1 << 21) +#define AUDIO_RX0_ADDSTDC (1 << 22) +#define AUDIO_RX0_ADDRXIN (1 << 23) + +#define AUDIO_RX1_PGARXEN (1 << 0); +#define AUDIO_RX1_PGASTEN (1 << 5) +#define AUDIO_RX1_ARXINEN (1 << 10) + +#define AUDIO_TX_AMC1REN (1 << 5) +#define AUDIO_TX_AMC1LEN (1 << 7) +#define AUDIO_TX_AMC2EN (1 << 9) +#define AUDIO_TX_ATXINEN (1 << 11) +#define AUDIO_TX_RXINREC (1 << 13) + +#define SSI_NETWORK_CDCTXRXSLOT(x) (((x) & 0x3) << 2) +#define SSI_NETWORK_CDCTXSECSLOT(x) (((x) & 0x3) << 4) +#define SSI_NETWORK_CDCRXSECSLOT(x) (((x) & 0x3) << 6) +#define SSI_NETWORK_CDCRXSECGAIN(x) (((x) & 0x3) << 8) +#define SSI_NETWORK_CDCSUMGAIN(x) (1 << 10) +#define SSI_NETWORK_CDCFSDLY(x) (1 << 11) +#define SSI_NETWORK_DAC_SLOTS_8 (1 << 12) +#define SSI_NETWORK_DAC_SLOTS_4 (2 << 12) +#define SSI_NETWORK_DAC_SLOTS_2 (3 << 12) +#define SSI_NETWORK_DAC_SLOT_MASK (3 << 12) +#define SSI_NETWORK_DAC_RXSLOT_0_1 (0 << 14) +#define SSI_NETWORK_DAC_RXSLOT_2_3 (1 << 14) +#define SSI_NETWORK_DAC_RXSLOT_4_5 (2 << 14) +#define SSI_NETWORK_DAC_RXSLOT_6_7 (3 << 14) +#define SSI_NETWORK_DAC_RXSLOT_MASK (3 << 14) +#define SSI_NETWORK_STDCRXSECSLOT(x) (((x) & 0x3) << 16) +#define SSI_NETWORK_STDCRXSECGAIN(x) (((x) & 0x3) << 18) +#define SSI_NETWORK_STDCSUMGAIN (1 << 20) + +/* + * MC13783_AUDIO_CODEC and MC13783_AUDIO_DAC mostly share the same + * register layout + */ +#define AUDIO_SSI_SEL (1 << 0) +#define AUDIO_CLK_SEL (1 << 1) +#define AUDIO_CSM (1 << 2) +#define AUDIO_BCL_INV (1 << 3) +#define AUDIO_CFS_INV (1 << 4) +#define AUDIO_CFS(x) (((x) & 0x3) << 5) +#define AUDIO_CLK(x) (((x) & 0x7) << 7) +#define AUDIO_C_EN (1 << 11) +#define AUDIO_C_CLK_EN (1 << 12) +#define AUDIO_C_RESET (1 << 15) + +#define AUDIO_CODEC_CDCFS8K16K (1 << 10) +#define AUDIO_DAC_CFS_DLY_B (1 << 10) + +struct mc13783_priv { + struct snd_soc_codec codec; + struct mc13xxx *mc13xxx; + + enum mc13783_ssi_port adc_ssi_port; + enum mc13783_ssi_port dac_ssi_port; +}; + +static unsigned int mc13783_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + struct mc13783_priv *priv = snd_soc_codec_get_drvdata(codec); + unsigned int value = 0; + + mc13xxx_lock(priv->mc13xxx); + + mc13xxx_reg_read(priv->mc13xxx, reg, &value); + + mc13xxx_unlock(priv->mc13xxx); + + return value; +} + +static int mc13783_write(struct snd_soc_codec *codec, + unsigned int reg, unsigned int value) +{ + struct mc13783_priv *priv = snd_soc_codec_get_drvdata(codec); + int ret; + + mc13xxx_lock(priv->mc13xxx); + + ret = mc13xxx_reg_write(priv->mc13xxx, reg, value); + + mc13xxx_unlock(priv->mc13xxx); + + return ret; +} + +/* Mapping between sample rates and register value */ +static unsigned int mc13783_rates[] = { + 8000, 11025, 12000, 16000, + 22050, 24000, 32000, 44100, + 48000, 64000, 96000 +}; + +static int mc13783_pcm_hw_params_dac(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_codec *codec = rtd->codec; + unsigned int rate = params_rate(params); + int i; + + for (i = 0; i < ARRAY_SIZE(mc13783_rates); i++) { + if (rate == mc13783_rates[i]) { + snd_soc_update_bits(codec, MC13783_AUDIO_DAC, + 0xf << 17, i << 17); + return 0; + } + } + + return -EINVAL; +} + +static int mc13783_pcm_hw_params_codec(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_codec *codec = rtd->codec; + unsigned int rate = params_rate(params); + unsigned int val; + + switch (rate) { + case 8000: + val = 0; + break; + case 16000: + val = AUDIO_CODEC_CDCFS8K16K; + break; + default: + return -EINVAL; + } + + snd_soc_update_bits(codec, MC13783_AUDIO_CODEC, AUDIO_CODEC_CDCFS8K16K, + val); + + return 0; +} + +static int mc13783_pcm_hw_params_sync(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + return mc13783_pcm_hw_params_dac(substream, params, dai); + else + return mc13783_pcm_hw_params_codec(substream, params, dai); +} + +static int mc13783_set_fmt(struct snd_soc_dai *dai, unsigned int fmt, + unsigned int reg) +{ + struct snd_soc_codec *codec = dai->codec; + unsigned int val = 0; + unsigned int mask = AUDIO_CFS(3) | AUDIO_BCL_INV | AUDIO_CFS_INV | + AUDIO_CSM | AUDIO_C_CLK_EN | AUDIO_C_RESET; + + + /* DAI mode */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + val |= AUDIO_CFS(2); + break; + case SND_SOC_DAIFMT_DSP_A: + val |= AUDIO_CFS(1); + break; + default: + return -EINVAL; + } + + /* DAI clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + val |= AUDIO_BCL_INV; + break; + case SND_SOC_DAIFMT_NB_IF: + val |= AUDIO_BCL_INV | AUDIO_CFS_INV; + break; + case SND_SOC_DAIFMT_IB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + val |= AUDIO_CFS_INV; + break; + } + + /* DAI clock master masks */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + val |= AUDIO_C_CLK_EN; + break; + case SND_SOC_DAIFMT_CBS_CFS: + val |= AUDIO_CSM; + break; + case SND_SOC_DAIFMT_CBM_CFS: + case SND_SOC_DAIFMT_CBS_CFM: + return -EINVAL; + } + + val |= AUDIO_C_RESET; + + snd_soc_update_bits(codec, reg, mask, val); + + return 0; +} + +static int mc13783_set_fmt_async(struct snd_soc_dai *dai, unsigned int fmt) +{ + if (dai->id == MC13783_ID_STEREO_DAC) + return mc13783_set_fmt(dai, fmt, MC13783_AUDIO_DAC); + else + return mc13783_set_fmt(dai, fmt, MC13783_AUDIO_CODEC); +} + +static int mc13783_set_fmt_sync(struct snd_soc_dai *dai, unsigned int fmt) +{ + int ret; + + ret = mc13783_set_fmt(dai, fmt, MC13783_AUDIO_DAC); + if (ret) + return ret; + + /* + * In synchronous mode force the voice codec into slave mode + * so that the clock / framesync from the stereo DAC is used + */ + fmt &= ~SND_SOC_DAIFMT_MASTER_MASK; + fmt |= SND_SOC_DAIFMT_CBS_CFS; + ret = mc13783_set_fmt(dai, fmt, MC13783_AUDIO_CODEC); + + return ret; +} + +static int mc13783_sysclk[] = { + 13000000, + 15360000, + 16800000, + -1, + 26000000, + -1, /* 12000000, invalid for voice codec */ + -1, /* 3686400, invalid for voice codec */ + 33600000, +}; + +static int mc13783_set_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir, + unsigned int reg) +{ + struct snd_soc_codec *codec = dai->codec; + int clk; + unsigned int val = 0; + unsigned int mask = AUDIO_CLK(0x7) | AUDIO_CLK_SEL; + + for (clk = 0; clk < ARRAY_SIZE(mc13783_sysclk); clk++) { + if (mc13783_sysclk[clk] < 0) + continue; + if (mc13783_sysclk[clk] == freq) + break; + } + + if (clk == ARRAY_SIZE(mc13783_sysclk)) + return -EINVAL; + + if (clk_id == MC13783_CLK_CLIB) + val |= AUDIO_CLK_SEL; + + val |= AUDIO_CLK(clk); + + snd_soc_update_bits(codec, reg, mask, val); + + return 0; +} + +static int mc13783_set_sysclk_dac(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + return mc13783_set_sysclk(dai, clk_id, freq, dir, MC13783_AUDIO_DAC); +} + +static int mc13783_set_sysclk_codec(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + return mc13783_set_sysclk(dai, clk_id, freq, dir, MC13783_AUDIO_CODEC); +} + +static int mc13783_set_sysclk_sync(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + int ret; + + ret = mc13783_set_sysclk(dai, clk_id, freq, dir, MC13783_AUDIO_DAC); + if (ret) + return ret; + + return mc13783_set_sysclk(dai, clk_id, freq, dir, MC13783_AUDIO_CODEC); +} + +static int mc13783_set_tdm_slot_dac(struct snd_soc_dai *dai, + unsigned int tx_mask, unsigned int rx_mask, int slots, + int slot_width) +{ + struct snd_soc_codec *codec = dai->codec; + unsigned int val = 0; + unsigned int mask = SSI_NETWORK_DAC_SLOT_MASK | + SSI_NETWORK_DAC_RXSLOT_MASK; + + switch (slots) { + case 2: + val |= SSI_NETWORK_DAC_SLOTS_2; + break; + case 4: + val |= SSI_NETWORK_DAC_SLOTS_4; + break; + case 8: + val |= SSI_NETWORK_DAC_SLOTS_8; + break; + default: + return -EINVAL; + } + + switch (rx_mask) { + case 0xfffffffc: + val |= SSI_NETWORK_DAC_RXSLOT_0_1; + break; + case 0xfffffff3: + val |= SSI_NETWORK_DAC_RXSLOT_2_3; + break; + case 0xffffffcf: + val |= SSI_NETWORK_DAC_RXSLOT_4_5; + break; + case 0xffffff3f: + val |= SSI_NETWORK_DAC_RXSLOT_6_7; + break; + default: + return -EINVAL; + }; + + snd_soc_update_bits(codec, MC13783_SSI_NETWORK, mask, val); + + return 0; +} + +static int mc13783_set_tdm_slot_codec(struct snd_soc_dai *dai, + unsigned int tx_mask, unsigned int rx_mask, int slots, + int slot_width) +{ + struct snd_soc_codec *codec = dai->codec; + unsigned int val = 0; + unsigned int mask = 0x3f; + + if (slots != 4) + return -EINVAL; + + if (tx_mask != 0xfffffffc) + return -EINVAL; + + val |= (0x00 << 2); /* primary timeslot RX/TX(?) is 0 */ + val |= (0x01 << 4); /* secondary timeslot TX is 1 */ + + snd_soc_update_bits(codec, MC13783_SSI_NETWORK, mask, val); + + return 0; +} + +static int mc13783_set_tdm_slot_sync(struct snd_soc_dai *dai, + unsigned int tx_mask, unsigned int rx_mask, int slots, + int slot_width) +{ + int ret; + + ret = mc13783_set_tdm_slot_dac(dai, tx_mask, rx_mask, slots, + slot_width); + if (ret) + return ret; + + ret = mc13783_set_tdm_slot_codec(dai, tx_mask, rx_mask, slots, + slot_width); + + return ret; +} + +static const struct snd_kcontrol_new mc1l_amp_ctl = + SOC_DAPM_SINGLE("Switch", 38, 7, 1, 0); + +static const struct snd_kcontrol_new mc1r_amp_ctl = + SOC_DAPM_SINGLE("Switch", 38, 5, 1, 0); + +static const struct snd_kcontrol_new mc2_amp_ctl = + SOC_DAPM_SINGLE("Switch", 38, 9, 1, 0); + +static const struct snd_kcontrol_new atx_amp_ctl = + SOC_DAPM_SINGLE("Switch", 38, 11, 1, 0); + + +/* Virtual mux. The chip does the input selection automatically + * as soon as we enable one input. */ +static const char * const adcl_enum_text[] = { + "MC1L", "RXINL", +}; + +static const struct soc_enum adcl_enum = + SOC_ENUM_SINGLE(0, 0, ARRAY_SIZE(adcl_enum_text), adcl_enum_text); + +static const struct snd_kcontrol_new left_input_mux = + SOC_DAPM_ENUM_VIRT("Route", adcl_enum); + +static const char * const adcr_enum_text[] = { + "MC1R", "MC2", "RXINR", "TXIN", +}; + +static const struct soc_enum adcr_enum = + SOC_ENUM_SINGLE(0, 0, ARRAY_SIZE(adcr_enum_text), adcr_enum_text); + +static const struct snd_kcontrol_new right_input_mux = + SOC_DAPM_ENUM_VIRT("Route", adcr_enum); + +static const struct snd_kcontrol_new samp_ctl = + SOC_DAPM_SINGLE("Switch", 36, 3, 1, 0); + +static const struct snd_kcontrol_new lamp_ctl = + SOC_DAPM_SINGLE("Switch", 36, 5, 1, 0); + +static const struct snd_kcontrol_new hlamp_ctl = + SOC_DAPM_SINGLE("Switch", 36, 10, 1, 0); + +static const struct snd_kcontrol_new hramp_ctl = + SOC_DAPM_SINGLE("Switch", 36, 9, 1, 0); + +static const struct snd_kcontrol_new llamp_ctl = + SOC_DAPM_SINGLE("Switch", 36, 16, 1, 0); + +static const struct snd_kcontrol_new lramp_ctl = + SOC_DAPM_SINGLE("Switch", 36, 15, 1, 0); + +static const struct snd_soc_dapm_widget mc13783_dapm_widgets[] = { +/* Input */ + SND_SOC_DAPM_INPUT("MC1LIN"), + SND_SOC_DAPM_INPUT("MC1RIN"), + SND_SOC_DAPM_INPUT("MC2IN"), + SND_SOC_DAPM_INPUT("RXINR"), + SND_SOC_DAPM_INPUT("RXINL"), + SND_SOC_DAPM_INPUT("TXIN"), + + SND_SOC_DAPM_SUPPLY("MC1 Bias", 38, 0, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("MC2 Bias", 38, 1, 0, NULL, 0), + + SND_SOC_DAPM_SWITCH("MC1L Amp", 38, 7, 0, &mc1l_amp_ctl), + SND_SOC_DAPM_SWITCH("MC1R Amp", 38, 5, 0, &mc1r_amp_ctl), + SND_SOC_DAPM_SWITCH("MC2 Amp", 38, 9, 0, &mc2_amp_ctl), + SND_SOC_DAPM_SWITCH("TXIN Amp", 38, 11, 0, &atx_amp_ctl), + + SND_SOC_DAPM_VIRT_MUX("PGA Left Input Mux", SND_SOC_NOPM, 0, 0, + &left_input_mux), + SND_SOC_DAPM_VIRT_MUX("PGA Right Input Mux", SND_SOC_NOPM, 0, 0, + &right_input_mux), + + SND_SOC_DAPM_PGA("PGA Left Input", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("PGA Right Input", SND_SOC_NOPM, 0, 0, NULL, 0), + + SND_SOC_DAPM_ADC("ADC", "Capture", 40, 11, 0), + SND_SOC_DAPM_SUPPLY("ADC_Reset", 40, 15, 0, NULL, 0), + +/* Output */ + SND_SOC_DAPM_SUPPLY("DAC_E", 41, 11, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("DAC_Reset", 41, 15, 0, NULL, 0), + SND_SOC_DAPM_OUTPUT("RXOUTL"), + SND_SOC_DAPM_OUTPUT("RXOUTR"), + SND_SOC_DAPM_OUTPUT("HSL"), + SND_SOC_DAPM_OUTPUT("HSR"), + SND_SOC_DAPM_OUTPUT("LSP"), + SND_SOC_DAPM_OUTPUT("SP"), + + SND_SOC_DAPM_SWITCH("Speaker Amp", 36, 3, 0, &samp_ctl), + SND_SOC_DAPM_SWITCH("Loudspeaker Amp", SND_SOC_NOPM, 0, 0, &lamp_ctl), + SND_SOC_DAPM_SWITCH("Headset Amp Left", 36, 10, 0, &hlamp_ctl), + SND_SOC_DAPM_SWITCH("Headset Amp Right", 36, 9, 0, &hramp_ctl), + SND_SOC_DAPM_SWITCH("Line out Amp Left", 36, 16, 0, &llamp_ctl), + SND_SOC_DAPM_SWITCH("Line out Amp Right", 36, 15, 0, &lramp_ctl), + SND_SOC_DAPM_DAC("DAC", "Playback", 36, 22, 0), + SND_SOC_DAPM_PGA("DAC PGA", 37, 5, 0, NULL, 0), +}; + +static struct snd_soc_dapm_route mc13783_routes[] = { +/* Input */ + { "MC1L Amp", NULL, "MC1LIN"}, + { "MC1R Amp", NULL, "MC1RIN" }, + { "MC2 Amp", NULL, "MC2IN" }, + { "TXIN Amp", NULL, "TXIN"}, + + { "PGA Left Input Mux", "MC1L", "MC1L Amp" }, + { "PGA Left Input Mux", "RXINL", "RXINL"}, + { "PGA Right Input Mux", "MC1R", "MC1R Amp" }, + { "PGA Right Input Mux", "MC2", "MC2 Amp"}, + { "PGA Right Input Mux", "TXIN", "TXIN Amp"}, + { "PGA Right Input Mux", "RXINR", "RXINR"}, + + { "PGA Left Input", NULL, "PGA Left Input Mux"}, + { "PGA Right Input", NULL, "PGA Right Input Mux"}, + + { "ADC", NULL, "PGA Left Input"}, + { "ADC", NULL, "PGA Right Input"}, + { "ADC", NULL, "ADC_Reset"}, + +/* Output */ + { "HSL", NULL, "Headset Amp Left" }, + { "HSR", NULL, "Headset Amp Right"}, + { "RXOUTL", NULL, "Line out Amp Left"}, + { "RXOUTR", NULL, "Line out Amp Right"}, + { "SP", NULL, "Speaker Amp"}, + { "Speaker Amp", NULL, "DAC PGA"}, + { "LSP", NULL, "DAC PGA"}, + { "Headset Amp Left", NULL, "DAC PGA"}, + { "Headset Amp Right", NULL, "DAC PGA"}, + { "Line out Amp Left", NULL, "DAC PGA"}, + { "Line out Amp Right", NULL, "DAC PGA"}, + { "DAC PGA", NULL, "DAC"}, + { "DAC", NULL, "DAC_E"}, +}; + +static const char * const mc13783_3d_mixer[] = {"Stereo", "Phase Mix", + "Mono", "Mono Mix"}; + +static const struct soc_enum mc13783_enum_3d_mixer = + SOC_ENUM_SINGLE(MC13783_AUDIO_RX1, 16, ARRAY_SIZE(mc13783_3d_mixer), + mc13783_3d_mixer); + +static struct snd_kcontrol_new mc13783_control_list[] = { + SOC_SINGLE("Loudspeaker enable", MC13783_AUDIO_RX0, 5, 1, 0), + SOC_SINGLE("PCM Playback Volume", MC13783_AUDIO_RX1, 6, 15, 0), + SOC_DOUBLE("PCM Capture Volume", MC13783_AUDIO_TX, 19, 14, 31, 0), + SOC_ENUM("3D Control", mc13783_enum_3d_mixer), +}; + +static int mc13783_probe(struct snd_soc_codec *codec) +{ + struct mc13783_priv *priv = snd_soc_codec_get_drvdata(codec); + + mc13xxx_lock(priv->mc13xxx); + + /* these are the reset values */ + mc13xxx_reg_write(priv->mc13xxx, MC13783_AUDIO_RX0, 0x25893); + mc13xxx_reg_write(priv->mc13xxx, MC13783_AUDIO_RX1, 0x00d35A); + mc13xxx_reg_write(priv->mc13xxx, MC13783_AUDIO_TX, 0x420000); + mc13xxx_reg_write(priv->mc13xxx, MC13783_SSI_NETWORK, 0x013060); + mc13xxx_reg_write(priv->mc13xxx, MC13783_AUDIO_CODEC, 0x180027); + mc13xxx_reg_write(priv->mc13xxx, MC13783_AUDIO_DAC, 0x0e0004); + + if (priv->adc_ssi_port == MC13783_SSI1_PORT) + mc13xxx_reg_rmw(priv->mc13xxx, MC13783_AUDIO_CODEC, + AUDIO_SSI_SEL, 0); + else + mc13xxx_reg_rmw(priv->mc13xxx, MC13783_AUDIO_CODEC, + 0, AUDIO_SSI_SEL); + + if (priv->dac_ssi_port == MC13783_SSI1_PORT) + mc13xxx_reg_rmw(priv->mc13xxx, MC13783_AUDIO_DAC, + AUDIO_SSI_SEL, 0); + else + mc13xxx_reg_rmw(priv->mc13xxx, MC13783_AUDIO_DAC, + 0, AUDIO_SSI_SEL); + + mc13xxx_unlock(priv->mc13xxx); + + return 0; +} + +static int mc13783_remove(struct snd_soc_codec *codec) +{ + struct mc13783_priv *priv = snd_soc_codec_get_drvdata(codec); + + mc13xxx_lock(priv->mc13xxx); + + /* Make sure VAUDIOON is off */ + mc13xxx_reg_rmw(priv->mc13xxx, MC13783_AUDIO_RX0, 0x3, 0); + + mc13xxx_unlock(priv->mc13xxx); + + return 0; +} + +#define MC13783_RATES_RECORD (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000) + +#define MC13783_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE) + +static struct snd_soc_dai_ops mc13783_ops_dac = { + .hw_params = mc13783_pcm_hw_params_dac, + .set_fmt = mc13783_set_fmt_async, + .set_sysclk = mc13783_set_sysclk_dac, + .set_tdm_slot = mc13783_set_tdm_slot_dac, +}; + +static struct snd_soc_dai_ops mc13783_ops_codec = { + .hw_params = mc13783_pcm_hw_params_codec, + .set_fmt = mc13783_set_fmt_async, + .set_sysclk = mc13783_set_sysclk_codec, + .set_tdm_slot = mc13783_set_tdm_slot_codec, +}; + +/* + * The mc13783 has two SSI ports, both of them can be routed either + * to the voice codec or the stereo DAC. When two different SSI ports + * are used for the voice codec and the stereo DAC we can do different + * formats and sysclock settings for playback and capture + * (mc13783-hifi-playback and mc13783-hifi-capture). Using the same port + * forces us to use symmetric rates (mc13783-hifi). + */ +static struct snd_soc_dai_driver mc13783_dai_async[] = { + { + .name = "mc13783-hifi-playback", + .id = MC13783_ID_STEREO_DAC, + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = MC13783_FORMATS, + }, + .ops = &mc13783_ops_dac, + }, { + .name = "mc13783-hifi-capture", + .id = MC13783_ID_STEREO_CODEC, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = MC13783_RATES_RECORD, + .formats = MC13783_FORMATS, + }, + .ops = &mc13783_ops_codec, + }, +}; + +static struct snd_soc_dai_ops mc13783_ops_sync = { + .hw_params = mc13783_pcm_hw_params_sync, + .set_fmt = mc13783_set_fmt_sync, + .set_sysclk = mc13783_set_sysclk_sync, + .set_tdm_slot = mc13783_set_tdm_slot_sync, +}; + +static struct snd_soc_dai_driver mc13783_dai_sync[] = { + { + .name = "mc13783-hifi", + .id = MC13783_ID_SYNC, + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = MC13783_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = MC13783_RATES_RECORD, + .formats = MC13783_FORMATS, + }, + .ops = &mc13783_ops_sync, + .symmetric_rates = 1, + } +}; + +static struct snd_soc_codec_driver soc_codec_dev_mc13783 = { + .probe = mc13783_probe, + .remove = mc13783_remove, + .read = mc13783_read, + .write = mc13783_write, + .controls = mc13783_control_list, + .num_controls = ARRAY_SIZE(mc13783_control_list), + .dapm_widgets = mc13783_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(mc13783_dapm_widgets), + .dapm_routes = mc13783_routes, + .num_dapm_routes = ARRAY_SIZE(mc13783_routes), +}; + +static int mc13783_codec_probe(struct platform_device *pdev) +{ + struct mc13xxx *mc13xxx; + struct mc13783_priv *priv; + struct mc13xxx_codec_platform_data *pdata = pdev->dev.platform_data; + int ret; + + mc13xxx = dev_get_drvdata(pdev->dev.parent); + + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (priv == NULL) + return -ENOMEM; + + dev_set_drvdata(&pdev->dev, priv); + priv->mc13xxx = mc13xxx; + if (pdata) { + priv->adc_ssi_port = pdata->adc_ssi_port; + priv->dac_ssi_port = pdata->dac_ssi_port; + } else { + priv->adc_ssi_port = MC13783_SSI1_PORT; + priv->dac_ssi_port = MC13783_SSI2_PORT; + } + + if (priv->adc_ssi_port == priv->dac_ssi_port) + ret = snd_soc_register_codec(&pdev->dev, &soc_codec_dev_mc13783, + mc13783_dai_sync, ARRAY_SIZE(mc13783_dai_sync)); + else + ret = snd_soc_register_codec(&pdev->dev, &soc_codec_dev_mc13783, + mc13783_dai_async, ARRAY_SIZE(mc13783_dai_async)); + + if (ret) + goto err_register_codec; + + return 0; + +err_register_codec: + dev_err(&pdev->dev, "register codec failed with %d\n", ret); + + return ret; +} + +static int mc13783_codec_remove(struct platform_device *pdev) +{ + snd_soc_unregister_codec(&pdev->dev); + + return 0; +} + +static struct platform_driver mc13783_codec_driver = { + .driver = { + .name = "mc13783-codec", + .owner = THIS_MODULE, + }, + .probe = mc13783_codec_probe, + .remove = __devexit_p(mc13783_codec_remove), +}; + +module_platform_driver(mc13783_codec_driver); + +MODULE_DESCRIPTION("ASoC MC13783 driver"); +MODULE_AUTHOR("Sascha Hauer, Pengutronix <s.hauer@pengutronix.de>"); +MODULE_AUTHOR("Philippe Retornaz <philippe.retornaz@epfl.ch>"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/mc13783.h b/sound/soc/codecs/mc13783.h new file mode 100644 index 00000000000..3a6d1993a21 --- /dev/null +++ b/sound/soc/codecs/mc13783.h @@ -0,0 +1,28 @@ +/* + * Copyright 2008 Juergen Beisert, kernel@pengutronix.de + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef MC13783_MIXER_H +#define MC13783_MIXER_H + +#define MC13783_CLK_CLIA 1 +#define MC13783_CLK_CLIB 2 + +#define MC13783_ID_STEREO_DAC 1 +#define MC13783_ID_STEREO_CODEC 2 +#define MC13783_ID_SYNC 3 + +#endif /* MC13783_MIXER_H */ diff --git a/sound/soc/codecs/ml26124.c b/sound/soc/codecs/ml26124.c new file mode 100644 index 00000000000..22cb5bf5927 --- /dev/null +++ b/sound/soc/codecs/ml26124.c @@ -0,0 +1,681 @@ +/* + * Copyright (C) 2011 LAPIS Semiconductor Co., Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/i2c.h> +#include <linux/slab.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/tlv.h> +#include "ml26124.h" + +#define DVOL_CTL_DVMUTE_ON BIT(4) /* Digital volume MUTE On */ +#define DVOL_CTL_DVMUTE_OFF 0 /* Digital volume MUTE Off */ +#define ML26124_SAI_NO_DELAY BIT(1) +#define ML26124_SAI_FRAME_SYNC (BIT(5) | BIT(0)) /* For mono (Telecodec) */ +#define ML26134_CACHESIZE 212 +#define ML26124_VMID BIT(1) +#define ML26124_RATES (SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_32000 |\ + SNDRV_PCM_RATE_48000) +#define ML26124_FORMATS (SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE |\ + SNDRV_PCM_FMTBIT_S32_LE) +#define ML26124_NUM_REGISTER ML26134_CACHESIZE + +struct ml26124_priv { + u32 mclk; + u32 rate; + struct regmap *regmap; + int clk_in; + struct snd_pcm_substream *substream; +}; + +struct clk_coeff { + u32 mclk; + u32 rate; + u8 pllnl; + u8 pllnh; + u8 pllml; + u8 pllmh; + u8 plldiv; +}; + +/* ML26124 configuration */ +static const DECLARE_TLV_DB_SCALE(digital_tlv, -7150, 50, 0); + +static const DECLARE_TLV_DB_SCALE(alclvl, -2250, 150, 0); +static const DECLARE_TLV_DB_SCALE(mingain, -1200, 600, 0); +static const DECLARE_TLV_DB_SCALE(maxgain, -675, 600, 0); +static const DECLARE_TLV_DB_SCALE(boost_vol, -1200, 75, 0); +static const DECLARE_TLV_DB_SCALE(ngth, -7650, 150, 0); + +static const char * const ml26124_companding[] = {"16bit PCM", "u-law", + "A-law"}; + +static const struct soc_enum ml26124_adc_companding_enum + = SOC_ENUM_SINGLE(ML26124_SAI_TRANS_CTL, 6, 3, ml26124_companding); + +static const struct soc_enum ml26124_dac_companding_enum + = SOC_ENUM_SINGLE(ML26124_SAI_RCV_CTL, 6, 3, ml26124_companding); + +static const struct snd_kcontrol_new ml26124_snd_controls[] = { + SOC_SINGLE_TLV("Capture Digital Volume", ML26124_RECORD_DIG_VOL, 0, + 0xff, 1, digital_tlv), + SOC_SINGLE_TLV("Playback Digital Volume", ML26124_PLBAK_DIG_VOL, 0, + 0xff, 1, digital_tlv), + SOC_SINGLE_TLV("Digital Boost Volume", ML26124_DIGI_BOOST_VOL, 0, + 0x3f, 0, boost_vol), + SOC_SINGLE_TLV("EQ Band0 Volume", ML26124_EQ_GAIN_BRAND0, 0, + 0xff, 1, digital_tlv), + SOC_SINGLE_TLV("EQ Band1 Volume", ML26124_EQ_GAIN_BRAND1, 0, + 0xff, 1, digital_tlv), + SOC_SINGLE_TLV("EQ Band2 Volume", ML26124_EQ_GAIN_BRAND2, 0, + 0xff, 1, digital_tlv), + SOC_SINGLE_TLV("EQ Band3 Volume", ML26124_EQ_GAIN_BRAND3, 0, + 0xff, 1, digital_tlv), + SOC_SINGLE_TLV("EQ Band4 Volume", ML26124_EQ_GAIN_BRAND4, 0, + 0xff, 1, digital_tlv), + SOC_SINGLE_TLV("ALC Target Level", ML26124_ALC_TARGET_LEV, 0, + 0xf, 1, alclvl), + SOC_SINGLE_TLV("ALC Min Input Volume", ML26124_ALC_MAXMIN_GAIN, 0, + 7, 0, mingain), + SOC_SINGLE_TLV("ALC Max Input Volume", ML26124_ALC_MAXMIN_GAIN, 4, + 7, 1, maxgain), + SOC_SINGLE_TLV("Playback Limiter Min Input Volume", + ML26124_PL_MAXMIN_GAIN, 0, 7, 0, mingain), + SOC_SINGLE_TLV("Playback Limiter Max Input Volume", + ML26124_PL_MAXMIN_GAIN, 4, 7, 1, maxgain), + SOC_SINGLE_TLV("Playback Boost Volume", ML26124_PLYBAK_BOST_VOL, 0, + 0x3f, 0, boost_vol), + SOC_SINGLE("DC High Pass Filter Switch", ML26124_FILTER_EN, 0, 1, 0), + SOC_SINGLE("Noise High Pass Filter Switch", ML26124_FILTER_EN, 1, 1, 0), + SOC_SINGLE("ZC Switch", ML26124_PW_ZCCMP_PW_MNG, 1, + 1, 0), + SOC_SINGLE("EQ Band0 Switch", ML26124_FILTER_EN, 2, 1, 0), + SOC_SINGLE("EQ Band1 Switch", ML26124_FILTER_EN, 3, 1, 0), + SOC_SINGLE("EQ Band2 Switch", ML26124_FILTER_EN, 4, 1, 0), + SOC_SINGLE("EQ Band3 Switch", ML26124_FILTER_EN, 5, 1, 0), + SOC_SINGLE("EQ Band4 Switch", ML26124_FILTER_EN, 6, 1, 0), + SOC_SINGLE("Play Limiter", ML26124_DVOL_CTL, 0, 1, 0), + SOC_SINGLE("Capture Limiter", ML26124_DVOL_CTL, 1, 1, 0), + SOC_SINGLE("Digital Volume Fade Switch", ML26124_DVOL_CTL, 3, 1, 0), + SOC_SINGLE("Digital Switch", ML26124_DVOL_CTL, 4, 1, 0), + SOC_ENUM("DAC Companding", ml26124_dac_companding_enum), + SOC_ENUM("ADC Companding", ml26124_adc_companding_enum), +}; + +static const struct snd_kcontrol_new ml26124_output_mixer_controls[] = { + SOC_DAPM_SINGLE("DAC Switch", ML26124_SPK_AMP_OUT, 1, 1, 0), + SOC_DAPM_SINGLE("Line in loopback Switch", ML26124_SPK_AMP_OUT, 3, 1, + 0), + SOC_DAPM_SINGLE("PGA Switch", ML26124_SPK_AMP_OUT, 5, 1, 0), +}; + +/* Input mux */ +static const char * const ml26124_input_select[] = {"Analog MIC SingleEnded in", + "Digital MIC in", "Analog MIC Differential in"}; + +static const struct soc_enum ml26124_insel_enum = + SOC_ENUM_SINGLE(ML26124_MIC_IF_CTL, 0, 3, ml26124_input_select); + +static const struct snd_kcontrol_new ml26124_input_mux_controls = + SOC_DAPM_ENUM("Input Select", ml26124_insel_enum); + +static const struct snd_kcontrol_new ml26124_line_control = + SOC_DAPM_SINGLE("Switch", ML26124_PW_LOUT_PW_MNG, 1, 1, 0); + +static const struct snd_soc_dapm_widget ml26124_dapm_widgets[] = { + SND_SOC_DAPM_SUPPLY("MCLKEN", ML26124_CLK_EN, 0, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("PLLEN", ML26124_CLK_EN, 1, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("PLLOE", ML26124_CLK_EN, 2, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("MICBIAS", ML26124_PW_REF_PW_MNG, 2, 0, NULL, 0), + SND_SOC_DAPM_MIXER("Output Mixer", SND_SOC_NOPM, 0, 0, + &ml26124_output_mixer_controls[0], + ARRAY_SIZE(ml26124_output_mixer_controls)), + SND_SOC_DAPM_DAC("DAC", "Playback", ML26124_PW_DAC_PW_MNG, 1, 0), + SND_SOC_DAPM_ADC("ADC", "Capture", ML26124_PW_IN_PW_MNG, 1, 0), + SND_SOC_DAPM_PGA("PGA", ML26124_PW_IN_PW_MNG, 3, 0, NULL, 0), + SND_SOC_DAPM_MUX("Input Mux", SND_SOC_NOPM, 0, 0, + &ml26124_input_mux_controls), + SND_SOC_DAPM_SWITCH("Line Out Enable", SND_SOC_NOPM, 0, 0, + &ml26124_line_control), + SND_SOC_DAPM_INPUT("MDIN"), + SND_SOC_DAPM_INPUT("MIN"), + SND_SOC_DAPM_INPUT("LIN"), + SND_SOC_DAPM_OUTPUT("SPOUT"), + SND_SOC_DAPM_OUTPUT("LOUT"), +}; + +static const struct snd_soc_dapm_route ml26124_intercon[] = { + /* Supply */ + {"DAC", NULL, "MCLKEN"}, + {"ADC", NULL, "MCLKEN"}, + {"DAC", NULL, "PLLEN"}, + {"ADC", NULL, "PLLEN"}, + {"DAC", NULL, "PLLOE"}, + {"ADC", NULL, "PLLOE"}, + + /* output mixer */ + {"Output Mixer", "DAC Switch", "DAC"}, + {"Output Mixer", "Line in loopback Switch", "LIN"}, + + /* outputs */ + {"LOUT", NULL, "Output Mixer"}, + {"SPOUT", NULL, "Output Mixer"}, + {"Line Out Enable", NULL, "LOUT"}, + + /* input */ + {"ADC", NULL, "Input Mux"}, + {"Input Mux", "Analog MIC SingleEnded in", "PGA"}, + {"Input Mux", "Analog MIC Differential in", "PGA"}, + {"PGA", NULL, "MIN"}, +}; + +/* PLLOutputFreq(Hz) = InputMclkFreq(Hz) * PLLM / (PLLN * PLLDIV) */ +static const struct clk_coeff coeff_div[] = { + {12288000, 16000, 0xc, 0x0, 0x20, 0x0, 0x4}, + {12288000, 32000, 0xc, 0x0, 0x20, 0x0, 0x4}, + {12288000, 48000, 0xc, 0x0, 0x30, 0x0, 0x4}, +}; + +static struct reg_default ml26124_reg[] = { + /* CLOCK control Register */ + {0x00, 0x00 }, /* Sampling Rate */ + {0x02, 0x00}, /* PLL NL */ + {0x04, 0x00}, /* PLLNH */ + {0x06, 0x00}, /* PLLML */ + {0x08, 0x00}, /* MLLMH */ + {0x0a, 0x00}, /* PLLDIV */ + {0x0c, 0x00}, /* Clock Enable */ + {0x0e, 0x00}, /* CLK Input/Output Control */ + + /* System Control Register */ + {0x10, 0x00}, /* Software RESET */ + {0x12, 0x00}, /* Record/Playback Run */ + {0x14, 0x00}, /* Mic Input/Output control */ + + /* Power Management Register */ + {0x20, 0x00}, /* Reference Power Management */ + {0x22, 0x00}, /* Input Power Management */ + {0x24, 0x00}, /* DAC Power Management */ + {0x26, 0x00}, /* SP-AMP Power Management */ + {0x28, 0x00}, /* LINEOUT Power Management */ + {0x2a, 0x00}, /* VIDEO Power Management */ + {0x2e, 0x00}, /* AC-CMP Power Management */ + + /* Analog reference Control Register */ + {0x30, 0x04}, /* MICBIAS Voltage Control */ + + /* Input/Output Amplifier Control Register */ + {0x32, 0x10}, /* MIC Input Volume */ + {0x38, 0x00}, /* Mic Boost Volume */ + {0x3a, 0x33}, /* Speaker AMP Volume */ + {0x48, 0x00}, /* AMP Volume Control Function Enable */ + {0x4a, 0x00}, /* Amplifier Volume Fader Control */ + + /* Analog Path Control Register */ + {0x54, 0x00}, /* Speaker AMP Output Control */ + {0x5a, 0x00}, /* Mic IF Control */ + {0xe8, 0x01}, /* Mic Select Control */ + + /* Audio Interface Control Register */ + {0x60, 0x00}, /* SAI-Trans Control */ + {0x62, 0x00}, /* SAI-Receive Control */ + {0x64, 0x00}, /* SAI Mode select */ + + /* DSP Control Register */ + {0x66, 0x01}, /* Filter Func Enable */ + {0x68, 0x00}, /* Volume Control Func Enable */ + {0x6A, 0x00}, /* Mixer & Volume Control*/ + {0x6C, 0xff}, /* Record Digital Volume */ + {0x70, 0xff}, /* Playback Digital Volume */ + {0x72, 0x10}, /* Digital Boost Volume */ + {0x74, 0xe7}, /* EQ gain Band0 */ + {0x76, 0xe7}, /* EQ gain Band1 */ + {0x78, 0xe7}, /* EQ gain Band2 */ + {0x7A, 0xe7}, /* EQ gain Band3 */ + {0x7C, 0xe7}, /* EQ gain Band4 */ + {0x7E, 0x00}, /* HPF2 CutOff*/ + {0x80, 0x00}, /* EQ Band0 Coef0L */ + {0x82, 0x00}, /* EQ Band0 Coef0H */ + {0x84, 0x00}, /* EQ Band0 Coef0L */ + {0x86, 0x00}, /* EQ Band0 Coef0H */ + {0x88, 0x00}, /* EQ Band1 Coef0L */ + {0x8A, 0x00}, /* EQ Band1 Coef0H */ + {0x8C, 0x00}, /* EQ Band1 Coef0L */ + {0x8E, 0x00}, /* EQ Band1 Coef0H */ + {0x90, 0x00}, /* EQ Band2 Coef0L */ + {0x92, 0x00}, /* EQ Band2 Coef0H */ + {0x94, 0x00}, /* EQ Band2 Coef0L */ + {0x96, 0x00}, /* EQ Band2 Coef0H */ + {0x98, 0x00}, /* EQ Band3 Coef0L */ + {0x9A, 0x00}, /* EQ Band3 Coef0H */ + {0x9C, 0x00}, /* EQ Band3 Coef0L */ + {0x9E, 0x00}, /* EQ Band3 Coef0H */ + {0xA0, 0x00}, /* EQ Band4 Coef0L */ + {0xA2, 0x00}, /* EQ Band4 Coef0H */ + {0xA4, 0x00}, /* EQ Band4 Coef0L */ + {0xA6, 0x00}, /* EQ Band4 Coef0H */ + + /* ALC Control Register */ + {0xb0, 0x00}, /* ALC Mode */ + {0xb2, 0x02}, /* ALC Attack Time */ + {0xb4, 0x03}, /* ALC Decay Time */ + {0xb6, 0x00}, /* ALC Hold Time */ + {0xb8, 0x0b}, /* ALC Target Level */ + {0xba, 0x70}, /* ALC Max/Min Gain */ + {0xbc, 0x00}, /* Noise Gate Threshold */ + {0xbe, 0x00}, /* ALC ZeroCross TimeOut */ + + /* Playback Limiter Control Register */ + {0xc0, 0x04}, /* PL Attack Time */ + {0xc2, 0x05}, /* PL Decay Time */ + {0xc4, 0x0d}, /* PL Target Level */ + {0xc6, 0x70}, /* PL Max/Min Gain */ + {0xc8, 0x10}, /* Playback Boost Volume */ + {0xca, 0x00}, /* PL ZeroCross TimeOut */ + + /* Video Amplifier Control Register */ + {0xd0, 0x01}, /* VIDEO AMP Gain Control */ + {0xd2, 0x01}, /* VIDEO AMP Setup 1 */ + {0xd4, 0x01}, /* VIDEO AMP Control2 */ +}; + +/* Get sampling rate value of sampling rate setting register (0x0) */ +static inline int get_srate(int rate) +{ + int srate; + + switch (rate) { + case 16000: + srate = 3; + break; + case 32000: + srate = 6; + break; + case 48000: + srate = 8; + break; + default: + return -EINVAL; + } + return srate; +} + +static inline int get_coeff(int mclk, int rate) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(coeff_div); i++) { + if (coeff_div[i].rate == rate && coeff_div[i].mclk == mclk) + return i; + } + return -EINVAL; +} + +static int ml26124_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct ml26124_priv *priv = snd_soc_codec_get_drvdata(codec); + int i = get_coeff(priv->mclk, params_rate(hw_params)); + + priv->substream = substream; + priv->rate = params_rate(hw_params); + + if (priv->clk_in) { + switch (priv->mclk / params_rate(hw_params)) { + case 256: + snd_soc_update_bits(codec, ML26124_CLK_CTL, + BIT(0) | BIT(1), 1); + break; + case 512: + snd_soc_update_bits(codec, ML26124_CLK_CTL, + BIT(0) | BIT(1), 2); + break; + case 1024: + snd_soc_update_bits(codec, ML26124_CLK_CTL, + BIT(0) | BIT(1), 3); + break; + default: + dev_err(codec->dev, "Unsupported MCLKI\n"); + break; + } + } else { + snd_soc_update_bits(codec, ML26124_CLK_CTL, + BIT(0) | BIT(1), 0); + } + + switch (params_rate(hw_params)) { + case 16000: + snd_soc_update_bits(codec, ML26124_SMPLING_RATE, 0xf, + get_srate(params_rate(hw_params))); + snd_soc_update_bits(codec, ML26124_PLLNL, 0xff, + coeff_div[i].pllnl); + snd_soc_update_bits(codec, ML26124_PLLNH, 0x1, + coeff_div[i].pllnh); + snd_soc_update_bits(codec, ML26124_PLLML, 0xff, + coeff_div[i].pllml); + snd_soc_update_bits(codec, ML26124_PLLMH, 0x3f, + coeff_div[i].pllmh); + snd_soc_update_bits(codec, ML26124_PLLDIV, 0x1f, + coeff_div[i].plldiv); + break; + case 32000: + snd_soc_update_bits(codec, ML26124_SMPLING_RATE, 0xf, + get_srate(params_rate(hw_params))); + snd_soc_update_bits(codec, ML26124_PLLNL, 0xff, + coeff_div[i].pllnl); + snd_soc_update_bits(codec, ML26124_PLLNH, 0x1, + coeff_div[i].pllnh); + snd_soc_update_bits(codec, ML26124_PLLML, 0xff, + coeff_div[i].pllml); + snd_soc_update_bits(codec, ML26124_PLLMH, 0x3f, + coeff_div[i].pllmh); + snd_soc_update_bits(codec, ML26124_PLLDIV, 0x1f, + coeff_div[i].plldiv); + break; + case 48000: + snd_soc_update_bits(codec, ML26124_SMPLING_RATE, 0xf, + get_srate(params_rate(hw_params))); + snd_soc_update_bits(codec, ML26124_PLLNL, 0xff, + coeff_div[i].pllnl); + snd_soc_update_bits(codec, ML26124_PLLNH, 0x1, + coeff_div[i].pllnh); + snd_soc_update_bits(codec, ML26124_PLLML, 0xff, + coeff_div[i].pllml); + snd_soc_update_bits(codec, ML26124_PLLMH, 0x3f, + coeff_div[i].pllmh); + snd_soc_update_bits(codec, ML26124_PLLDIV, 0x1f, + coeff_div[i].plldiv); + break; + default: + pr_err("%s:this rate is no support for ml26124\n", __func__); + return -EINVAL; + } + + return 0; +} + +static int ml26124_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + struct ml26124_priv *priv = snd_soc_codec_get_drvdata(codec); + + switch (priv->substream->stream) { + case SNDRV_PCM_STREAM_CAPTURE: + snd_soc_update_bits(codec, ML26124_REC_PLYBAK_RUN, BIT(0), 1); + break; + case SNDRV_PCM_STREAM_PLAYBACK: + snd_soc_update_bits(codec, ML26124_REC_PLYBAK_RUN, BIT(1), 2); + break; + } + + if (mute) + snd_soc_update_bits(codec, ML26124_DVOL_CTL, BIT(4), + DVOL_CTL_DVMUTE_ON); + else + snd_soc_update_bits(codec, ML26124_DVOL_CTL, BIT(4), + DVOL_CTL_DVMUTE_OFF); + + return 0; +} + +static int ml26124_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + unsigned char mode; + struct snd_soc_codec *codec = codec_dai->codec; + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + mode = 1; + break; + case SND_SOC_DAIFMT_CBS_CFS: + mode = 0; + break; + default: + return -EINVAL; + } + snd_soc_update_bits(codec, ML26124_SAI_MODE_SEL, BIT(0), mode); + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + break; + default: + return -EINVAL; + } + + /* clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + default: + return -EINVAL; + } + + return 0; +} + +static int ml26124_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 ml26124_priv *priv = snd_soc_codec_get_drvdata(codec); + + switch (clk_id) { + case ML26124_USE_PLLOUT: + priv->clk_in = ML26124_USE_PLLOUT; + break; + case ML26124_USE_MCLKI: + priv->clk_in = ML26124_USE_MCLKI; + break; + default: + return -EINVAL; + } + + priv->mclk = freq; + + return 0; +} + +static int ml26124_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + struct ml26124_priv *priv = snd_soc_codec_get_drvdata(codec); + + switch (level) { + case SND_SOC_BIAS_ON: + snd_soc_update_bits(codec, ML26124_PW_SPAMP_PW_MNG, + ML26124_R26_MASK, ML26124_BLT_PREAMP_ON); + msleep(100); + snd_soc_update_bits(codec, ML26124_PW_SPAMP_PW_MNG, + ML26124_R26_MASK, + ML26124_MICBEN_ON | ML26124_BLT_ALL_ON); + break; + case SND_SOC_BIAS_PREPARE: + break; + case SND_SOC_BIAS_STANDBY: + /* VMID ON */ + if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) { + snd_soc_update_bits(codec, ML26124_PW_REF_PW_MNG, + ML26124_VMID, ML26124_VMID); + msleep(500); + regcache_sync(priv->regmap); + } + break; + case SND_SOC_BIAS_OFF: + /* VMID OFF */ + snd_soc_update_bits(codec, ML26124_PW_REF_PW_MNG, + ML26124_VMID, 0); + break; + } + codec->dapm.bias_level = level; + return 0; +} + +static const struct snd_soc_dai_ops ml26124_dai_ops = { + .hw_params = ml26124_hw_params, + .digital_mute = ml26124_mute, + .set_fmt = ml26124_set_dai_fmt, + .set_sysclk = ml26124_set_dai_sysclk, +}; + +static struct snd_soc_dai_driver ml26124_dai = { + .name = "ml26124-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = ML26124_RATES, + .formats = ML26124_FORMATS,}, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = ML26124_RATES, + .formats = ML26124_FORMATS,}, + .ops = &ml26124_dai_ops, + .symmetric_rates = 1, +}; + +#ifdef CONFIG_PM +static int ml26124_suspend(struct snd_soc_codec *codec) +{ + ml26124_set_bias_level(codec, SND_SOC_BIAS_OFF); + + return 0; +} + +static int ml26124_resume(struct snd_soc_codec *codec) +{ + ml26124_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + return 0; +} +#else +#define ml26124_suspend NULL +#define ml26124_resume NULL +#endif + +static int ml26124_probe(struct snd_soc_codec *codec) +{ + int ret; + struct ml26124_priv *priv = snd_soc_codec_get_drvdata(codec); + codec->control_data = priv->regmap; + + ret = snd_soc_codec_set_cache_io(codec, 7, 9, SND_SOC_REGMAP); + if (ret < 0) { + dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret); + return ret; + } + + /* Software Reset */ + snd_soc_update_bits(codec, ML26124_SW_RST, 0x01, 1); + snd_soc_update_bits(codec, ML26124_SW_RST, 0x01, 0); + + ml26124_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + return 0; +} + +static struct snd_soc_codec_driver soc_codec_dev_ml26124 = { + .probe = ml26124_probe, + .suspend = ml26124_suspend, + .resume = ml26124_resume, + .set_bias_level = ml26124_set_bias_level, + .dapm_widgets = ml26124_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(ml26124_dapm_widgets), + .dapm_routes = ml26124_intercon, + .num_dapm_routes = ARRAY_SIZE(ml26124_intercon), + .controls = ml26124_snd_controls, + .num_controls = ARRAY_SIZE(ml26124_snd_controls), +}; + +static const struct regmap_config ml26124_i2c_regmap = { + .val_bits = 8, + .reg_bits = 8, + .max_register = ML26124_NUM_REGISTER, + .reg_defaults = ml26124_reg, + .num_reg_defaults = ARRAY_SIZE(ml26124_reg), + .cache_type = REGCACHE_RBTREE, + .write_flag_mask = 0x01, +}; + +static __devinit int ml26124_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct ml26124_priv *priv; + int ret; + + priv = devm_kzalloc(&i2c->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + i2c_set_clientdata(i2c, priv); + + priv->regmap = regmap_init_i2c(i2c, &ml26124_i2c_regmap); + if (IS_ERR(priv->regmap)) { + ret = PTR_ERR(priv->regmap); + dev_err(&i2c->dev, "regmap_init_i2c() failed: %d\n", ret); + return ret; + } + + return snd_soc_register_codec(&i2c->dev, + &soc_codec_dev_ml26124, &ml26124_dai, 1); +} + +static __devexit int ml26124_i2c_remove(struct i2c_client *client) +{ + struct ml26124_priv *priv = i2c_get_clientdata(client); + + snd_soc_unregister_codec(&client->dev); + regmap_exit(priv->regmap); + return 0; +} + +static const struct i2c_device_id ml26124_i2c_id[] = { + { "ml26124", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ml26124_i2c_id); + +static struct i2c_driver ml26124_i2c_driver = { + .driver = { + .name = "ml26124", + .owner = THIS_MODULE, + }, + .probe = ml26124_i2c_probe, + .remove = __devexit_p(ml26124_i2c_remove), + .id_table = ml26124_i2c_id, +}; + +module_i2c_driver(ml26124_i2c_driver); + +MODULE_AUTHOR("Tomoya MORINAGA <tomoya.rohm@gmail.com>"); +MODULE_DESCRIPTION("LAPIS Semiconductor ML26124 ALSA SoC codec driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/ml26124.h b/sound/soc/codecs/ml26124.h new file mode 100644 index 00000000000..5ea0cbb8c46 --- /dev/null +++ b/sound/soc/codecs/ml26124.h @@ -0,0 +1,184 @@ +/* + * Copyright (C) 2011 LAPIS Semiconductor Co., Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef ML26124_H +#define ML26124_H + +/* Clock Control Register */ +#define ML26124_SMPLING_RATE 0x00 +#define ML26124_PLLNL 0x02 +#define ML26124_PLLNH 0x04 +#define ML26124_PLLML 0x06 +#define ML26124_PLLMH 0x08 +#define ML26124_PLLDIV 0x0a +#define ML26124_CLK_EN 0x0c +#define ML26124_CLK_CTL 0x0e + +/* System Control Register */ +#define ML26124_SW_RST 0x10 +#define ML26124_REC_PLYBAK_RUN 0x12 +#define ML26124_MIC_TIM 0x14 + +/* Power Mnagement Register */ +#define ML26124_PW_REF_PW_MNG 0x20 +#define ML26124_PW_IN_PW_MNG 0x22 +#define ML26124_PW_DAC_PW_MNG 0x24 +#define ML26124_PW_SPAMP_PW_MNG 0x26 +#define ML26124_PW_LOUT_PW_MNG 0x28 +#define ML26124_PW_VOUT_PW_MNG 0x2a +#define ML26124_PW_ZCCMP_PW_MNG 0x2e + +/* Analog Reference Control Register */ +#define ML26124_PW_MICBIAS_VOL 0x30 + +/* Input/Output Amplifier Control Register */ +#define ML26124_PW_MIC_IN_VOL 0x32 +#define ML26124_PW_MIC_BOST_VOL 0x38 +#define ML26124_PW_SPK_AMP_VOL 0x3a +#define ML26124_PW_AMP_VOL_FUNC 0x48 +#define ML26124_PW_AMP_VOL_FADE 0x4a + +/* Analog Path Control Register */ +#define ML26124_SPK_AMP_OUT 0x54 +#define ML26124_MIC_IF_CTL 0x5a +#define ML26124_MIC_SELECT 0xe8 + +/* Audio Interface Control Register */ +#define ML26124_SAI_TRANS_CTL 0x60 +#define ML26124_SAI_RCV_CTL 0x62 +#define ML26124_SAI_MODE_SEL 0x64 + +/* DSP Control Register */ +#define ML26124_FILTER_EN 0x66 +#define ML26124_DVOL_CTL 0x68 +#define ML26124_MIXER_VOL_CTL 0x6a +#define ML26124_RECORD_DIG_VOL 0x6c +#define ML26124_PLBAK_DIG_VOL 0x70 +#define ML26124_DIGI_BOOST_VOL 0x72 +#define ML26124_EQ_GAIN_BRAND0 0x74 +#define ML26124_EQ_GAIN_BRAND1 0x76 +#define ML26124_EQ_GAIN_BRAND2 0x78 +#define ML26124_EQ_GAIN_BRAND3 0x7a +#define ML26124_EQ_GAIN_BRAND4 0x7c +#define ML26124_HPF2_CUTOFF 0x7e +#define ML26124_EQBRAND0_F0L 0x80 +#define ML26124_EQBRAND0_F0H 0x82 +#define ML26124_EQBRAND0_F1L 0x84 +#define ML26124_EQBRAND0_F1H 0x86 +#define ML26124_EQBRAND1_F0L 0x88 +#define ML26124_EQBRAND1_F0H 0x8a +#define ML26124_EQBRAND1_F1L 0x8c +#define ML26124_EQBRAND1_F1H 0x8e +#define ML26124_EQBRAND2_F0L 0x90 +#define ML26124_EQBRAND2_F0H 0x92 +#define ML26124_EQBRAND2_F1L 0x94 +#define ML26124_EQBRAND2_F1H 0x96 +#define ML26124_EQBRAND3_F0L 0x98 +#define ML26124_EQBRAND3_F0H 0x9a +#define ML26124_EQBRAND3_F1L 0x9c +#define ML26124_EQBRAND3_F1H 0x9e +#define ML26124_EQBRAND4_F0L 0xa0 +#define ML26124_EQBRAND4_F0H 0xa2 +#define ML26124_EQBRAND4_F1L 0xa4 +#define ML26124_EQBRAND4_F1H 0xa6 + +/* ALC Control Register */ +#define ML26124_ALC_MODE 0xb0 +#define ML26124_ALC_ATTACK_TIM 0xb2 +#define ML26124_ALC_DECAY_TIM 0xb4 +#define ML26124_ALC_HOLD_TIM 0xb6 +#define ML26124_ALC_TARGET_LEV 0xb8 +#define ML26124_ALC_MAXMIN_GAIN 0xba +#define ML26124_NOIS_GATE_THRSH 0xbc +#define ML26124_ALC_ZERO_TIMOUT 0xbe + +/* Playback Limiter Control Register */ +#define ML26124_PL_ATTACKTIME 0xc0 +#define ML26124_PL_DECAYTIME 0xc2 +#define ML26124_PL_TARGETTIME 0xc4 +#define ML26124_PL_MAXMIN_GAIN 0xc6 +#define ML26124_PLYBAK_BOST_VOL 0xc8 +#define ML26124_PL_0CROSS_TIMOUT 0xca + +/* Video Amplifer Control Register */ +#define ML26124_VIDEO_AMP_GAIN_CTL 0xd0 +#define ML26124_VIDEO_AMP_SETUP1 0xd2 +#define ML26124_VIDEO_AMP_CTL2 0xd4 + +/* Clock select for machine driver */ +#define ML26124_USE_PLL 0 +#define ML26124_USE_MCLKI_256FS 1 +#define ML26124_USE_MCLKI_512FS 2 +#define ML26124_USE_MCLKI_1024FS 3 + +/* Register Mask */ +#define ML26124_R0_MASK 0xf +#define ML26124_R2_MASK 0xff +#define ML26124_R4_MASK 0x1 +#define ML26124_R6_MASK 0xf +#define ML26124_R8_MASK 0x3f +#define ML26124_Ra_MASK 0x1f +#define ML26124_Rc_MASK 0x1f +#define ML26124_Re_MASK 0x7 +#define ML26124_R10_MASK 0x1 +#define ML26124_R12_MASK 0x17 +#define ML26124_R14_MASK 0x3f +#define ML26124_R20_MASK 0x47 +#define ML26124_R22_MASK 0xa +#define ML26124_R24_MASK 0x2 +#define ML26124_R26_MASK 0x1f +#define ML26124_R28_MASK 0x2 +#define ML26124_R2a_MASK 0x2 +#define ML26124_R2e_MASK 0x2 +#define ML26124_R30_MASK 0x7 +#define ML26124_R32_MASK 0x3f +#define ML26124_R38_MASK 0x38 +#define ML26124_R3a_MASK 0x3f +#define ML26124_R48_MASK 0x3 +#define ML26124_R4a_MASK 0x7 +#define ML26124_R54_MASK 0x2a +#define ML26124_R5a_MASK 0x3 +#define ML26124_Re8_MASK 0x3 +#define ML26124_R60_MASK 0xff +#define ML26124_R62_MASK 0xff +#define ML26124_R64_MASK 0x1 +#define ML26124_R66_MASK 0xff +#define ML26124_R68_MASK 0x3b +#define ML26124_R6a_MASK 0xf3 +#define ML26124_R6c_MASK 0xff +#define ML26124_R70_MASK 0xff + +#define ML26124_MCLKEN BIT(0) +#define ML26124_PLLEN BIT(1) +#define ML26124_PLLOE BIT(2) +#define ML26124_MCLKOE BIT(3) + +#define ML26124_BLT_ALL_ON 0x1f +#define ML26124_BLT_PREAMP_ON 0x13 + +#define ML26124_MICBEN_ON BIT(2) + +enum ml26124_regs { + ML26124_MCLK = 0, +}; + +enum ml26124_clk_in { + ML26124_USE_PLLOUT = 0, + ML26124_USE_MCLKI, +}; + +#endif diff --git a/sound/soc/codecs/omap-hdmi.c b/sound/soc/codecs/omap-hdmi.c new file mode 100644 index 00000000000..1bf5c74f5f9 --- /dev/null +++ b/sound/soc/codecs/omap-hdmi.c @@ -0,0 +1,69 @@ +/* + * ALSA SoC codec driver for HDMI audio on OMAP processors. + * Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com/ + * Author: Ricardo Neri <ricardo.neri@ti.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. + * + * 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 <sound/soc.h> + +#define DRV_NAME "hdmi-audio-codec" + +static struct snd_soc_codec_driver omap_hdmi_codec; + +static struct snd_soc_dai_driver omap_hdmi_codec_dai = { + .name = "omap-hdmi-hifi", + .playback = { + .channels_min = 2, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_32000 | + SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_176400 | SNDRV_PCM_RATE_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE, + }, +}; + +static __devinit int omap_hdmi_codec_probe(struct platform_device *pdev) +{ + return snd_soc_register_codec(&pdev->dev, &omap_hdmi_codec, + &omap_hdmi_codec_dai, 1); +} + +static __devexit int omap_hdmi_codec_remove(struct platform_device *pdev) +{ + snd_soc_unregister_codec(&pdev->dev); + return 0; +} + +static struct platform_driver omap_hdmi_codec_driver = { + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + }, + + .probe = omap_hdmi_codec_probe, + .remove = __devexit_p(omap_hdmi_codec_remove), +}; + +module_platform_driver(omap_hdmi_codec_driver); + +MODULE_AUTHOR("Ricardo Neri <ricardo.neri@ti.com>"); +MODULE_DESCRIPTION("ASoC OMAP HDMI codec driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRV_NAME); diff --git a/sound/soc/codecs/rt5631.c b/sound/soc/codecs/rt5631.c index 20c324c7c34..960d0e93cce 100644 --- a/sound/soc/codecs/rt5631.c +++ b/sound/soc/codecs/rt5631.c @@ -18,7 +18,7 @@ #include <linux/delay.h> #include <linux/pm.h> #include <linux/i2c.h> -#include <linux/spi/spi.h> +#include <linux/regmap.h> #include <sound/core.h> #include <sound/pcm.h> #include <sound/pcm_params.h> @@ -30,6 +30,7 @@ #include "rt5631.h" struct rt5631_priv { + struct regmap *regmap; int codec_version; int master; int sysclk; @@ -38,33 +39,33 @@ struct rt5631_priv { int dmic_used_flag; }; -static const u16 rt5631_reg[RT5631_VENDOR_ID2 + 1] = { - [RT5631_SPK_OUT_VOL] = 0x8888, - [RT5631_HP_OUT_VOL] = 0x8080, - [RT5631_MONO_AXO_1_2_VOL] = 0xa080, - [RT5631_AUX_IN_VOL] = 0x0808, - [RT5631_ADC_REC_MIXER] = 0xf0f0, - [RT5631_VDAC_DIG_VOL] = 0x0010, - [RT5631_OUTMIXER_L_CTRL] = 0xffc0, - [RT5631_OUTMIXER_R_CTRL] = 0xffc0, - [RT5631_AXO1MIXER_CTRL] = 0x88c0, - [RT5631_AXO2MIXER_CTRL] = 0x88c0, - [RT5631_DIG_MIC_CTRL] = 0x3000, - [RT5631_MONO_INPUT_VOL] = 0x8808, - [RT5631_SPK_MIXER_CTRL] = 0xf8f8, - [RT5631_SPK_MONO_OUT_CTRL] = 0xfc00, - [RT5631_SPK_MONO_HP_OUT_CTRL] = 0x4440, - [RT5631_SDP_CTRL] = 0x8000, - [RT5631_MONO_SDP_CTRL] = 0x8000, - [RT5631_STEREO_AD_DA_CLK_CTRL] = 0x2010, - [RT5631_GEN_PUR_CTRL_REG] = 0x0e00, - [RT5631_INT_ST_IRQ_CTRL_2] = 0x071a, - [RT5631_MISC_CTRL] = 0x2040, - [RT5631_DEPOP_FUN_CTRL_2] = 0x8000, - [RT5631_SOFT_VOL_CTRL] = 0x07e0, - [RT5631_ALC_CTRL_1] = 0x0206, - [RT5631_ALC_CTRL_3] = 0x2000, - [RT5631_PSEUDO_SPATL_CTRL] = 0x0553, +static const struct reg_default rt5631_reg[] = { + { RT5631_SPK_OUT_VOL, 0x8888 }, + { RT5631_HP_OUT_VOL, 0x8080 }, + { RT5631_MONO_AXO_1_2_VOL, 0xa080 }, + { RT5631_AUX_IN_VOL, 0x0808 }, + { RT5631_ADC_REC_MIXER, 0xf0f0 }, + { RT5631_VDAC_DIG_VOL, 0x0010 }, + { RT5631_OUTMIXER_L_CTRL, 0xffc0 }, + { RT5631_OUTMIXER_R_CTRL, 0xffc0 }, + { RT5631_AXO1MIXER_CTRL, 0x88c0 }, + { RT5631_AXO2MIXER_CTRL, 0x88c0 }, + { RT5631_DIG_MIC_CTRL, 0x3000 }, + { RT5631_MONO_INPUT_VOL, 0x8808 }, + { RT5631_SPK_MIXER_CTRL, 0xf8f8 }, + { RT5631_SPK_MONO_OUT_CTRL, 0xfc00 }, + { RT5631_SPK_MONO_HP_OUT_CTRL, 0x4440 }, + { RT5631_SDP_CTRL, 0x8000 }, + { RT5631_MONO_SDP_CTRL, 0x8000 }, + { RT5631_STEREO_AD_DA_CLK_CTRL, 0x2010 }, + { RT5631_GEN_PUR_CTRL_REG, 0x0e00 }, + { RT5631_INT_ST_IRQ_CTRL_2, 0x071a }, + { RT5631_MISC_CTRL, 0x2040 }, + { RT5631_DEPOP_FUN_CTRL_2, 0x8000 }, + { RT5631_SOFT_VOL_CTRL, 0x07e0 }, + { RT5631_ALC_CTRL_1, 0x0206 }, + { RT5631_ALC_CTRL_3, 0x2000 }, + { RT5631_PSEUDO_SPATL_CTRL, 0x0553 }, }; /** @@ -96,8 +97,7 @@ static int rt5631_reset(struct snd_soc_codec *codec) return snd_soc_write(codec, RT5631_RESET, 0); } -static int rt5631_volatile_register(struct snd_soc_codec *codec, - unsigned int reg) +static bool rt5631_volatile_register(struct device *dev, unsigned int reg) { switch (reg) { case RT5631_RESET: @@ -111,8 +111,7 @@ static int rt5631_volatile_register(struct snd_soc_codec *codec, } } -static int rt5631_readable_register(struct snd_soc_codec *codec, - unsigned int reg) +static bool rt5631_readable_register(struct device *dev, unsigned int reg) { switch (reg) { case RT5631_RESET: @@ -1361,8 +1360,7 @@ static int get_coeff(int mclk, int rate, int timesofbclk) static int rt5631_hifi_pcm_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_codec *codec = dai->codec; struct rt5631_priv *rt5631 = snd_soc_codec_get_drvdata(codec); int timesofbclk = 32, coeff; unsigned int iface = 0; @@ -1544,6 +1542,8 @@ static int rt5631_codec_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id, static int rt5631_set_bias_level(struct snd_soc_codec *codec, enum snd_soc_bias_level level) { + struct rt5631_priv *rt5631 = snd_soc_codec_get_drvdata(codec); + switch (level) { case SND_SOC_BIAS_ON: case SND_SOC_BIAS_PREPARE: @@ -1561,8 +1561,8 @@ static int rt5631_set_bias_level(struct snd_soc_codec *codec, snd_soc_update_bits(codec, RT5631_PWR_MANAG_ADD3, RT5631_PWR_FAST_VREF_CTRL, RT5631_PWR_FAST_VREF_CTRL); - codec->cache_only = false; - snd_soc_cache_sync(codec); + regcache_cache_only(rt5631->regmap, false); + regcache_sync(rt5631->regmap); } break; @@ -1587,7 +1587,9 @@ static int rt5631_probe(struct snd_soc_codec *codec) unsigned int val; int ret; - ret = snd_soc_codec_set_cache_io(codec, 8, 16, SND_SOC_I2C); + codec->control_data = rt5631->regmap; + + ret = snd_soc_codec_set_cache_io(codec, 8, 16, SND_SOC_REGMAP); if (ret != 0) { dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret); return ret; @@ -1698,12 +1700,6 @@ static struct snd_soc_codec_driver soc_codec_dev_rt5631 = { .suspend = rt5631_suspend, .resume = rt5631_resume, .set_bias_level = rt5631_set_bias_level, - .reg_cache_size = RT5631_VENDOR_ID2 + 1, - .reg_word_size = sizeof(u16), - .reg_cache_default = rt5631_reg, - .volatile_register = rt5631_volatile_register, - .readable_register = rt5631_readable_register, - .reg_cache_step = 1, .controls = rt5631_snd_controls, .num_controls = ARRAY_SIZE(rt5631_snd_controls), .dapm_widgets = rt5631_dapm_widgets, @@ -1718,6 +1714,18 @@ static const struct i2c_device_id rt5631_i2c_id[] = { }; MODULE_DEVICE_TABLE(i2c, rt5631_i2c_id); +static const struct regmap_config rt5631_regmap_config = { + .reg_bits = 8, + .val_bits = 16, + + .readable_reg = rt5631_readable_register, + .volatile_reg = rt5631_volatile_register, + .max_register = RT5631_VENDOR_ID2, + .reg_defaults = rt5631_reg, + .num_reg_defaults = ARRAY_SIZE(rt5631_reg), + .cache_type = REGCACHE_RBTREE, +}; + static int rt5631_i2c_probe(struct i2c_client *i2c, const struct i2c_device_id *id) { @@ -1731,6 +1739,10 @@ static int rt5631_i2c_probe(struct i2c_client *i2c, i2c_set_clientdata(i2c, rt5631); + rt5631->regmap = devm_regmap_init_i2c(i2c, &rt5631_regmap_config); + if (IS_ERR(rt5631->regmap)) + return PTR_ERR(rt5631->regmap); + ret = snd_soc_register_codec(&i2c->dev, &soc_codec_dev_rt5631, rt5631_dai, ARRAY_SIZE(rt5631_dai)); return ret; @@ -1752,17 +1764,7 @@ static struct i2c_driver rt5631_i2c_driver = { .id_table = rt5631_i2c_id, }; -static int __init rt5631_modinit(void) -{ - return i2c_add_driver(&rt5631_i2c_driver); -} -module_init(rt5631_modinit); - -static void __exit rt5631_modexit(void) -{ - i2c_del_driver(&rt5631_i2c_driver); -} -module_exit(rt5631_modexit); +module_i2c_driver(rt5631_i2c_driver); MODULE_DESCRIPTION("ASoC RT5631 driver"); MODULE_AUTHOR("flove <flove@realtek.com>"); diff --git a/sound/soc/codecs/sgtl5000.c b/sound/soc/codecs/sgtl5000.c index c395ec37044..8af6a5245b1 100644 --- a/sound/soc/codecs/sgtl5000.c +++ b/sound/soc/codecs/sgtl5000.c @@ -84,8 +84,8 @@ static struct regulator_consumer_supply ldo_consumer[] = { static struct regulator_init_data ldo_init_data = { .constraints = { - .min_uV = 850000, - .max_uV = 1600000, + .min_uV = 1200000, + .max_uV = 1200000, .valid_modes_mask = REGULATOR_MODE_NORMAL, .valid_ops_mask = REGULATOR_CHANGE_STATUS, }, @@ -197,9 +197,9 @@ static const struct snd_soc_dapm_widget sgtl5000_dapm_widgets[] = { SND_SOC_DAPM_OUTPUT("HP_OUT"), SND_SOC_DAPM_OUTPUT("LINE_OUT"), - SND_SOC_DAPM_MICBIAS_E("Mic Bias", SGTL5000_CHIP_MIC_CTRL, 8, 0, - mic_bias_event, - SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_SUPPLY("Mic Bias", SGTL5000_CHIP_MIC_CTRL, 8, 0, + mic_bias_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), SND_SOC_DAPM_PGA("HP", SGTL5000_CHIP_ANA_POWER, 4, 0, NULL, 0), SND_SOC_DAPM_PGA("LO", SGTL5000_CHIP_ANA_POWER, 0, 0, NULL, 0), @@ -665,8 +665,7 @@ static int sgtl5000_pcm_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_codec *codec = dai->codec; struct sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(codec); int channels = params_channels(params); int i2s_ctl = 0; @@ -1455,17 +1454,7 @@ static struct i2c_driver sgtl5000_i2c_driver = { .id_table = sgtl5000_id, }; -static int __init sgtl5000_modinit(void) -{ - return i2c_add_driver(&sgtl5000_i2c_driver); -} -module_init(sgtl5000_modinit); - -static void __exit sgtl5000_exit(void) -{ - i2c_del_driver(&sgtl5000_i2c_driver); -} -module_exit(sgtl5000_exit); +module_i2c_driver(sgtl5000_i2c_driver); MODULE_DESCRIPTION("Freescale SGTL5000 ALSA SoC Codec Driver"); MODULE_AUTHOR("Zeng Zhaoming <zengzm.kernel@gmail.com>"); diff --git a/sound/soc/codecs/ssm2602.c b/sound/soc/codecs/ssm2602.c index de2b20544ce..079066fef42 100644 --- a/sound/soc/codecs/ssm2602.c +++ b/sound/soc/codecs/ssm2602.c @@ -33,6 +33,7 @@ #include <linux/pm.h> #include <linux/i2c.h> #include <linux/spi/spi.h> +#include <linux/regmap.h> #include <linux/slab.h> #include <sound/core.h> #include <sound/pcm.h> @@ -43,8 +44,6 @@ #include "ssm2602.h" -#define SSM2602_VERSION "0.1" - enum ssm2602_type { SSM2602, SSM2604, @@ -53,10 +52,12 @@ enum ssm2602_type { /* codec private data */ struct ssm2602_priv { unsigned int sysclk; - enum snd_soc_control_type control_type; + struct snd_pcm_hw_constraint_list *sysclk_constraints; struct snd_pcm_substream *master_substream; struct snd_pcm_substream *slave_substream; + struct regmap *regmap; + enum ssm2602_type type; unsigned int clk_out_pwr; }; @@ -73,7 +74,6 @@ static const u16 ssm2602_reg[SSM2602_CACHEREGNUM] = { 0x0000, 0x0000 }; -#define ssm2602_reset(c) snd_soc_write(c, SSM2602_RESET, 0) /*Appending several "None"s just for OSS mixer use*/ static const char *ssm2602_input_select[] = { @@ -195,6 +195,24 @@ static const struct snd_soc_dapm_route ssm2604_routes[] = { {"ADC", NULL, "Line Input"}, }; +static const unsigned int ssm2602_rates_12288000[] = { + 8000, 32000, 48000, 96000, +}; + +static struct snd_pcm_hw_constraint_list ssm2602_constraints_12288000 = { + .list = ssm2602_rates_12288000, + .count = ARRAY_SIZE(ssm2602_rates_12288000), +}; + +static const unsigned int ssm2602_rates_11289600[] = { + 8000, 44100, 88200, +}; + +static struct snd_pcm_hw_constraint_list ssm2602_constraints_11289600 = { + .list = ssm2602_rates_11289600, + .count = ARRAY_SIZE(ssm2602_rates_11289600), +}; + struct ssm2602_coeff { u32 mclk; u32 rate; @@ -254,11 +272,10 @@ static int ssm2602_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_codec *codec = dai->codec; struct ssm2602_priv *ssm2602 = snd_soc_codec_get_drvdata(codec); - u16 iface = snd_soc_read(codec, SSM2602_IFACE) & 0xfff3; int srate = ssm2602_get_coeff(ssm2602->sysclk, params_rate(params)); + unsigned int iface; if (substream == ssm2602->slave_substream) { dev_dbg(codec->dev, "Ignoring hw_params for slave substream\n"); @@ -268,31 +285,34 @@ static int ssm2602_hw_params(struct snd_pcm_substream *substream, if (srate < 0) return srate; - snd_soc_write(codec, SSM2602_SRATE, srate); + regmap_write(ssm2602->regmap, SSM2602_SRATE, srate); /* bit size */ switch (params_format(params)) { case SNDRV_PCM_FORMAT_S16_LE: + iface = 0x0; break; case SNDRV_PCM_FORMAT_S20_3LE: - iface |= 0x0004; + iface = 0x4; break; case SNDRV_PCM_FORMAT_S24_LE: - iface |= 0x0008; + iface = 0x8; break; case SNDRV_PCM_FORMAT_S32_LE: - iface |= 0x000c; + iface = 0xc; break; + default: + return -EINVAL; } - snd_soc_write(codec, SSM2602_IFACE, iface); + regmap_update_bits(ssm2602->regmap, SSM2602_IFACE, + IFACE_AUDIO_DATA_LEN, iface); return 0; } static int ssm2602_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_codec *codec = dai->codec; struct ssm2602_priv *ssm2602 = snd_soc_codec_get_drvdata(codec); struct snd_pcm_runtime *master_runtime; @@ -322,14 +342,19 @@ static int ssm2602_startup(struct snd_pcm_substream *substream, } else ssm2602->master_substream = substream; + if (ssm2602->sysclk_constraints) { + snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + ssm2602->sysclk_constraints); + } + return 0; } static void ssm2602_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_codec *codec = dai->codec; struct ssm2602_priv *ssm2602 = snd_soc_codec_get_drvdata(codec); if (ssm2602->master_substream == substream) @@ -341,14 +366,14 @@ static void ssm2602_shutdown(struct snd_pcm_substream *substream, static int ssm2602_mute(struct snd_soc_dai *dai, int mute) { - struct snd_soc_codec *codec = dai->codec; + struct ssm2602_priv *ssm2602 = snd_soc_codec_get_drvdata(dai->codec); if (mute) - snd_soc_update_bits(codec, SSM2602_APDIGI, + regmap_update_bits(ssm2602->regmap, SSM2602_APDIGI, APDIGI_ENABLE_DAC_MUTE, APDIGI_ENABLE_DAC_MUTE); else - snd_soc_update_bits(codec, SSM2602_APDIGI, + regmap_update_bits(ssm2602->regmap, SSM2602_APDIGI, APDIGI_ENABLE_DAC_MUTE, 0); return 0; } @@ -364,16 +389,21 @@ static int ssm2602_set_dai_sysclk(struct snd_soc_dai *codec_dai, return -EINVAL; switch (freq) { - case 11289600: - case 12000000: case 12288000: - case 16934400: case 18432000: - ssm2602->sysclk = freq; + ssm2602->sysclk_constraints = &ssm2602_constraints_12288000; + break; + case 11289600: + case 16934400: + ssm2602->sysclk_constraints = &ssm2602_constraints_11289600; + break; + case 12000000: + ssm2602->sysclk_constraints = NULL; break; default: return -EINVAL; } + ssm2602->sysclk = freq; } else { unsigned int mask; @@ -393,7 +423,7 @@ static int ssm2602_set_dai_sysclk(struct snd_soc_dai *codec_dai, else ssm2602->clk_out_pwr &= ~mask; - snd_soc_update_bits(codec, SSM2602_PWR, + regmap_update_bits(ssm2602->regmap, SSM2602_PWR, PWR_CLK_OUT_PDN | PWR_OSC_PDN, ssm2602->clk_out_pwr); } @@ -403,8 +433,8 @@ static int ssm2602_set_dai_sysclk(struct snd_soc_dai *codec_dai, static int ssm2602_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) { - struct snd_soc_codec *codec = codec_dai->codec; - u16 iface = 0; + struct ssm2602_priv *ssm2602 = snd_soc_codec_get_drvdata(codec_dai->codec); + unsigned int iface = 0; /* set master/slave audio interface */ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { @@ -455,7 +485,7 @@ static int ssm2602_set_dai_fmt(struct snd_soc_dai *codec_dai, } /* set iface */ - snd_soc_write(codec, SSM2602_IFACE, iface); + regmap_write(ssm2602->regmap, SSM2602_IFACE, iface); return 0; } @@ -467,7 +497,7 @@ static int ssm2602_set_bias_level(struct snd_soc_codec *codec, switch (level) { case SND_SOC_BIAS_ON: /* vref/mid on, osc and clkout on if enabled */ - snd_soc_update_bits(codec, SSM2602_PWR, + regmap_update_bits(ssm2602->regmap, SSM2602_PWR, PWR_POWER_OFF | PWR_CLK_OUT_PDN | PWR_OSC_PDN, ssm2602->clk_out_pwr); break; @@ -475,13 +505,13 @@ static int ssm2602_set_bias_level(struct snd_soc_codec *codec, break; case SND_SOC_BIAS_STANDBY: /* everything off except vref/vmid, */ - snd_soc_update_bits(codec, SSM2602_PWR, + regmap_update_bits(ssm2602->regmap, SSM2602_PWR, PWR_POWER_OFF | PWR_CLK_OUT_PDN | PWR_OSC_PDN, PWR_CLK_OUT_PDN | PWR_OSC_PDN); break; case SND_SOC_BIAS_OFF: /* everything off */ - snd_soc_update_bits(codec, SSM2602_PWR, + regmap_update_bits(ssm2602->regmap, SSM2602_PWR, PWR_POWER_OFF, PWR_POWER_OFF); break; @@ -540,12 +570,13 @@ static int ssm2602_resume(struct snd_soc_codec *codec) static int ssm2602_probe(struct snd_soc_codec *codec) { + struct ssm2602_priv *ssm2602 = snd_soc_codec_get_drvdata(codec); struct snd_soc_dapm_context *dapm = &codec->dapm; int ret; - snd_soc_update_bits(codec, SSM2602_LOUT1V, + regmap_update_bits(ssm2602->regmap, SSM2602_LOUT1V, LOUT1V_LRHP_BOTH, LOUT1V_LRHP_BOTH); - snd_soc_update_bits(codec, SSM2602_ROUT1V, + regmap_update_bits(ssm2602->regmap, SSM2602_ROUT1V, ROUT1V_RLHP_BOTH, ROUT1V_RLHP_BOTH); ret = snd_soc_add_codec_controls(codec, ssm2602_snd_controls, @@ -581,27 +612,26 @@ static int ssm260x_probe(struct snd_soc_codec *codec) struct ssm2602_priv *ssm2602 = snd_soc_codec_get_drvdata(codec); int ret; - pr_info("ssm2602 Audio Codec %s", SSM2602_VERSION); - - ret = snd_soc_codec_set_cache_io(codec, 7, 9, ssm2602->control_type); + codec->control_data = ssm2602->regmap; + ret = snd_soc_codec_set_cache_io(codec, 0, 0, SND_SOC_REGMAP); if (ret < 0) { dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret); return ret; } - ret = ssm2602_reset(codec); + ret = regmap_write(ssm2602->regmap, SSM2602_RESET, 0); if (ret < 0) { dev_err(codec->dev, "Failed to issue reset: %d\n", ret); return ret; } /* set the update bits */ - snd_soc_update_bits(codec, SSM2602_LINVOL, + regmap_update_bits(ssm2602->regmap, SSM2602_LINVOL, LINVOL_LRIN_BOTH, LINVOL_LRIN_BOTH); - snd_soc_update_bits(codec, SSM2602_RINVOL, + regmap_update_bits(ssm2602->regmap, SSM2602_RINVOL, RINVOL_RLIN_BOTH, RINVOL_RLIN_BOTH); /*select Line in as default input*/ - snd_soc_write(codec, SSM2602_APANA, APANA_SELECT_DAC | + regmap_write(ssm2602->regmap, SSM2602_APANA, APANA_SELECT_DAC | APANA_ENABLE_MIC_BOOST); switch (ssm2602->type) { @@ -634,9 +664,6 @@ static struct snd_soc_codec_driver soc_codec_dev_ssm2602 = { .suspend = ssm2602_suspend, .resume = ssm2602_resume, .set_bias_level = ssm2602_set_bias_level, - .reg_cache_size = ARRAY_SIZE(ssm2602_reg), - .reg_word_size = sizeof(u16), - .reg_cache_default = ssm2602_reg, .controls = ssm260x_snd_controls, .num_controls = ARRAY_SIZE(ssm260x_snd_controls), @@ -646,6 +673,23 @@ static struct snd_soc_codec_driver soc_codec_dev_ssm2602 = { .num_dapm_routes = ARRAY_SIZE(ssm260x_routes), }; +static bool ssm2602_register_volatile(struct device *dev, unsigned int reg) +{ + return reg == SSM2602_RESET; +} + +static const struct regmap_config ssm2602_regmap_config = { + .val_bits = 9, + .reg_bits = 7, + + .max_register = SSM2602_RESET, + .volatile_reg = ssm2602_register_volatile, + + .cache_type = REGCACHE_RBTREE, + .reg_defaults_raw = ssm2602_reg, + .num_reg_defaults_raw = ARRAY_SIZE(ssm2602_reg), +}; + #if defined(CONFIG_SPI_MASTER) static int __devinit ssm2602_spi_probe(struct spi_device *spi) { @@ -658,9 +702,12 @@ static int __devinit ssm2602_spi_probe(struct spi_device *spi) return -ENOMEM; spi_set_drvdata(spi, ssm2602); - ssm2602->control_type = SND_SOC_SPI; ssm2602->type = SSM2602; + ssm2602->regmap = devm_regmap_init_spi(spi, &ssm2602_regmap_config); + if (IS_ERR(ssm2602->regmap)) + return PTR_ERR(ssm2602->regmap); + ret = snd_soc_register_codec(&spi->dev, &soc_codec_dev_ssm2602, &ssm2602_dai, 1); return ret; @@ -701,9 +748,12 @@ static int __devinit ssm2602_i2c_probe(struct i2c_client *i2c, return -ENOMEM; i2c_set_clientdata(i2c, ssm2602); - ssm2602->control_type = SND_SOC_I2C; ssm2602->type = id->driver_data; + ssm2602->regmap = devm_regmap_init_i2c(i2c, &ssm2602_regmap_config); + if (IS_ERR(ssm2602->regmap)) + return PTR_ERR(ssm2602->regmap); + ret = snd_soc_register_codec(&i2c->dev, &soc_codec_dev_ssm2602, &ssm2602_dai, 1); return ret; diff --git a/sound/soc/codecs/sta32x.c b/sound/soc/codecs/sta32x.c index 7db6fa51502..8d717f4b5a8 100644 --- a/sound/soc/codecs/sta32x.c +++ b/sound/soc/codecs/sta32x.c @@ -609,8 +609,7 @@ static int sta32x_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_codec *codec = dai->codec; struct sta32x_priv *sta32x = snd_soc_codec_get_drvdata(codec); unsigned int rate; int i, mcs = -1, ir = -1; diff --git a/sound/soc/codecs/tlv320aic23.c b/sound/soc/codecs/tlv320aic23.c index df1e07ffac3..31762ebdd77 100644 --- a/sound/soc/codecs/tlv320aic23.c +++ b/sound/soc/codecs/tlv320aic23.c @@ -34,8 +34,6 @@ #include "tlv320aic23.h" -#define AIC23_VERSION "0.1" - /* * AIC23 register cache */ @@ -325,8 +323,7 @@ static int tlv320aic23_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_codec *codec = dai->codec; u16 iface_reg; int ret; struct aic23 *aic23 = snd_soc_codec_get_drvdata(codec); @@ -371,8 +368,7 @@ static int tlv320aic23_hw_params(struct snd_pcm_substream *substream, static int tlv320aic23_pcm_prepare(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_codec *codec = dai->codec; /* set active */ snd_soc_write(codec, TLV320AIC23_ACTIVE, 0x0001); @@ -383,8 +379,7 @@ static int tlv320aic23_pcm_prepare(struct snd_pcm_substream *substream, static void tlv320aic23_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_codec *codec = dai->codec; struct aic23 *aic23 = snd_soc_codec_get_drvdata(codec); /* deactivate */ @@ -548,8 +543,6 @@ static int tlv320aic23_probe(struct snd_soc_codec *codec) struct aic23 *aic23 = snd_soc_codec_get_drvdata(codec); int ret; - printk(KERN_INFO "AIC23 Audio Codec %s\n", AIC23_VERSION); - ret = snd_soc_codec_set_cache_io(codec, 7, 9, aic23->control_type); if (ret < 0) { dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret); diff --git a/sound/soc/codecs/tlv320aic26.c b/sound/soc/codecs/tlv320aic26.c index 802064b5030..85944e95357 100644 --- a/sound/soc/codecs/tlv320aic26.c +++ b/sound/soc/codecs/tlv320aic26.c @@ -126,8 +126,7 @@ static int aic26_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_codec *codec = dai->codec; struct aic26 *aic26 = snd_soc_codec_get_drvdata(codec); int fsref, divisor, wlen, pval, jval, dval, qval; u16 reg; diff --git a/sound/soc/codecs/tlv320aic3x.c b/sound/soc/codecs/tlv320aic3x.c index 8d20f6ec20f..64d2a4fa34b 100644 --- a/sound/soc/codecs/tlv320aic3x.c +++ b/sound/soc/codecs/tlv320aic3x.c @@ -802,8 +802,7 @@ static int aic3x_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_codec *codec =rtd->codec; + struct snd_soc_codec *codec = dai->codec; struct aic3x_priv *aic3x = snd_soc_codec_get_drvdata(codec); int codec_clk = 0, bypass_pll = 0, fsref, last_clk = 0; u8 data, j, r, p, pll_q, pll_p = 1, pll_r = 1, pll_j = 1; @@ -1161,24 +1160,6 @@ static int aic3x_set_bias_level(struct snd_soc_codec *codec, return 0; } -void aic3x_set_headset_detection(struct snd_soc_codec *codec, int detect, - int headset_debounce, int button_debounce) -{ - u8 val; - - val = ((detect & AIC3X_HEADSET_DETECT_MASK) - << AIC3X_HEADSET_DETECT_SHIFT) | - ((headset_debounce & AIC3X_HEADSET_DEBOUNCE_MASK) - << AIC3X_HEADSET_DEBOUNCE_SHIFT) | - ((button_debounce & AIC3X_BUTTON_DEBOUNCE_MASK) - << AIC3X_BUTTON_DEBOUNCE_SHIFT); - - if (detect & AIC3X_HEADSET_DETECT_MASK) - val |= AIC3X_HEADSET_DETECT_ENABLED; - - snd_soc_write(codec, AIC3X_HEADSET_DETECT_CTRL_A, val); -} - #define AIC3X_RATES SNDRV_PCM_RATE_8000_96000 #define AIC3X_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S32_LE) diff --git a/sound/soc/codecs/tlv320dac33.c b/sound/soc/codecs/tlv320dac33.c index 4587ddd0fbf..0dd41077ab7 100644 --- a/sound/soc/codecs/tlv320dac33.c +++ b/sound/soc/codecs/tlv320dac33.c @@ -62,8 +62,10 @@ #define UTHR_FROM_PERIOD_SIZE(samples, playrate, burstrate) \ (((samples)*5000) / (((burstrate)*5000) / ((burstrate) - (playrate)))) -static void dac33_calculate_times(struct snd_pcm_substream *substream); -static int dac33_prepare_chip(struct snd_pcm_substream *substream); +static void dac33_calculate_times(struct snd_pcm_substream *substream, + struct snd_soc_codec *codec); +static int dac33_prepare_chip(struct snd_pcm_substream *substream, + struct snd_soc_codec *codec); enum dac33_state { DAC33_IDLE = 0, @@ -427,8 +429,8 @@ static int dac33_playback_event(struct snd_soc_dapm_widget *w, switch (event) { case SND_SOC_DAPM_PRE_PMU: if (likely(dac33->substream)) { - dac33_calculate_times(dac33->substream); - dac33_prepare_chip(dac33->substream); + dac33_calculate_times(dac33->substream, w->codec); + dac33_prepare_chip(dac33->substream, w->codec); } break; case SND_SOC_DAPM_POST_PMD: @@ -799,8 +801,7 @@ static void dac33_oscwait(struct snd_soc_codec *codec) static int dac33_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_codec *codec = dai->codec; struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec); /* Stream started, save the substream pointer */ @@ -812,8 +813,7 @@ static int dac33_startup(struct snd_pcm_substream *substream, static void dac33_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_codec *codec = dai->codec; struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec); dac33->substream = NULL; @@ -825,8 +825,7 @@ static int dac33_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_codec *codec = dai->codec; struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec); /* Check parameters for validity */ @@ -868,10 +867,9 @@ static int dac33_hw_params(struct snd_pcm_substream *substream, * writes happens in different order, than dac33 might end up in unknown state. * Use the known, working sequence of register writes to initialize the dac33. */ -static int dac33_prepare_chip(struct snd_pcm_substream *substream) +static int dac33_prepare_chip(struct snd_pcm_substream *substream, + struct snd_soc_codec *codec) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_codec *codec = rtd->codec; struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec); unsigned int oscset, ratioset, pwr_ctrl, reg_tmp; u8 aictrl_a, aictrl_b, fifoctrl_a; @@ -1067,10 +1065,9 @@ static int dac33_prepare_chip(struct snd_pcm_substream *substream) return 0; } -static void dac33_calculate_times(struct snd_pcm_substream *substream) +static void dac33_calculate_times(struct snd_pcm_substream *substream, + struct snd_soc_codec *codec) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_codec *codec = rtd->codec; struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec); unsigned int period_size = substream->runtime->period_size; unsigned int rate = substream->runtime->rate; @@ -1128,8 +1125,7 @@ static void dac33_calculate_times(struct snd_pcm_substream *substream) static int dac33_pcm_trigger(struct snd_pcm_substream *substream, int cmd, struct snd_soc_dai *dai) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_codec *codec = dai->codec; struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec); int ret = 0; @@ -1161,8 +1157,7 @@ static snd_pcm_sframes_t dac33_dai_delay( struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_codec *codec = dai->codec; struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec); unsigned long long t0, t1, t_now; unsigned int time_delta, uthr; diff --git a/sound/soc/codecs/twl4030.c b/sound/soc/codecs/twl4030.c index 170cf9a8fc7..391fcfc7b63 100644 --- a/sound/soc/codecs/twl4030.c +++ b/sound/soc/codecs/twl4030.c @@ -1685,8 +1685,7 @@ static void twl4030_tdm_enable(struct snd_soc_codec *codec, int direction, static int twl4030_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_codec *codec = dai->codec; struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(codec); if (twl4030->master_substream) { @@ -1715,8 +1714,7 @@ static int twl4030_startup(struct snd_pcm_substream *substream, static void twl4030_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_codec *codec = dai->codec; struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(codec); if (twl4030->master_substream == substream) @@ -1740,8 +1738,7 @@ static int twl4030_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_codec *codec = dai->codec; struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(codec); u8 mode, old_mode, format, old_format; @@ -1974,8 +1971,7 @@ static void twl4030_voice_enable(struct snd_soc_codec *codec, int direction, static int twl4030_voice_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_codec *codec = dai->codec; struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(codec); u8 mode; @@ -2007,8 +2003,7 @@ static int twl4030_voice_startup(struct snd_pcm_substream *substream, static void twl4030_voice_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_codec *codec = dai->codec; /* Enable voice digital filters */ twl4030_voice_enable(codec, substream->stream, 0); @@ -2017,8 +2012,7 @@ static void twl4030_voice_shutdown(struct snd_pcm_substream *substream, static int twl4030_voice_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_codec *codec = dai->codec; struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(codec); u8 old_mode, mode; diff --git a/sound/soc/codecs/twl6040.c b/sound/soc/codecs/twl6040.c index dc7509b9d53..a36e9fcdf18 100644 --- a/sound/soc/codecs/twl6040.c +++ b/sound/soc/codecs/twl6040.c @@ -46,17 +46,6 @@ #define TWL6040_OUTHF_0dB 0x03 #define TWL6040_OUTHF_M52dB 0x1D -#define TWL6040_RAMP_NONE 0 -#define TWL6040_RAMP_UP 1 -#define TWL6040_RAMP_DOWN 2 - -#define TWL6040_HSL_VOL_MASK 0x0F -#define TWL6040_HSL_VOL_SHIFT 0 -#define TWL6040_HSR_VOL_MASK 0xF0 -#define TWL6040_HSR_VOL_SHIFT 4 -#define TWL6040_HF_VOL_MASK 0x1F -#define TWL6040_HF_VOL_SHIFT 0 - /* Shadow register used by the driver */ #define TWL6040_REG_SW_SHADOW 0x2F #define TWL6040_CACHEREGNUM (TWL6040_REG_SW_SHADOW + 1) @@ -64,18 +53,6 @@ /* TWL6040_REG_SW_SHADOW (0x2F) fields */ #define TWL6040_EAR_PATH_ENABLE 0x01 -struct twl6040_output { - u16 active; - u16 left_vol; - u16 right_vol; - u16 left_step; - u16 right_step; - unsigned int step_delay; - u16 ramp; - struct delayed_work work; - struct completion ramp_done; -}; - struct twl6040_jack_data { struct snd_soc_jack *jack; struct delayed_work work; @@ -100,8 +77,6 @@ struct twl6040_data { struct snd_soc_codec *codec; struct workqueue_struct *workqueue; struct mutex mutex; - struct twl6040_output headset; - struct twl6040_output handsfree; }; /* @@ -311,318 +286,6 @@ static void twl6040_restore_regs(struct snd_soc_codec *codec) } } -/* - * Ramp HS PGA volume to minimise pops at stream startup and shutdown. - */ -static inline int twl6040_hs_ramp_step(struct snd_soc_codec *codec, - unsigned int left_step, unsigned int right_step) -{ - - struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec); - struct twl6040_output *headset = &priv->headset; - int left_complete = 0, right_complete = 0; - u8 reg, val; - - /* left channel */ - left_step = (left_step > 0xF) ? 0xF : left_step; - reg = twl6040_read_reg_cache(codec, TWL6040_REG_HSGAIN); - val = (~reg & TWL6040_HSL_VOL_MASK); - - if (headset->ramp == TWL6040_RAMP_UP) { - /* ramp step up */ - if (val < headset->left_vol) { - if (val + left_step > headset->left_vol) - val = headset->left_vol; - else - val += left_step; - - reg &= ~TWL6040_HSL_VOL_MASK; - twl6040_write(codec, TWL6040_REG_HSGAIN, - (reg | (~val & TWL6040_HSL_VOL_MASK))); - } else { - left_complete = 1; - } - } else if (headset->ramp == TWL6040_RAMP_DOWN) { - /* ramp step down */ - if (val > 0x0) { - if ((int)val - (int)left_step < 0) - val = 0; - else - val -= left_step; - - reg &= ~TWL6040_HSL_VOL_MASK; - twl6040_write(codec, TWL6040_REG_HSGAIN, reg | - (~val & TWL6040_HSL_VOL_MASK)); - } else { - left_complete = 1; - } - } - - /* right channel */ - right_step = (right_step > 0xF) ? 0xF : right_step; - reg = twl6040_read_reg_cache(codec, TWL6040_REG_HSGAIN); - val = (~reg & TWL6040_HSR_VOL_MASK) >> TWL6040_HSR_VOL_SHIFT; - - if (headset->ramp == TWL6040_RAMP_UP) { - /* ramp step up */ - if (val < headset->right_vol) { - if (val + right_step > headset->right_vol) - val = headset->right_vol; - else - val += right_step; - - reg &= ~TWL6040_HSR_VOL_MASK; - twl6040_write(codec, TWL6040_REG_HSGAIN, - (reg | (~val << TWL6040_HSR_VOL_SHIFT))); - } else { - right_complete = 1; - } - } else if (headset->ramp == TWL6040_RAMP_DOWN) { - /* ramp step down */ - if (val > 0x0) { - if ((int)val - (int)right_step < 0) - val = 0; - else - val -= right_step; - - reg &= ~TWL6040_HSR_VOL_MASK; - twl6040_write(codec, TWL6040_REG_HSGAIN, - reg | (~val << TWL6040_HSR_VOL_SHIFT)); - } else { - right_complete = 1; - } - } - - return left_complete & right_complete; -} - -/* - * Ramp HF PGA volume to minimise pops at stream startup and shutdown. - */ -static inline int twl6040_hf_ramp_step(struct snd_soc_codec *codec, - unsigned int left_step, unsigned int right_step) -{ - struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec); - struct twl6040_output *handsfree = &priv->handsfree; - int left_complete = 0, right_complete = 0; - u16 reg, val; - - /* left channel */ - left_step = (left_step > 0x1D) ? 0x1D : left_step; - reg = twl6040_read_reg_cache(codec, TWL6040_REG_HFLGAIN); - reg = 0x1D - reg; - val = (reg & TWL6040_HF_VOL_MASK); - if (handsfree->ramp == TWL6040_RAMP_UP) { - /* ramp step up */ - if (val < handsfree->left_vol) { - if (val + left_step > handsfree->left_vol) - val = handsfree->left_vol; - else - val += left_step; - - reg &= ~TWL6040_HF_VOL_MASK; - twl6040_write(codec, TWL6040_REG_HFLGAIN, - reg | (0x1D - val)); - } else { - left_complete = 1; - } - } else if (handsfree->ramp == TWL6040_RAMP_DOWN) { - /* ramp step down */ - if (val > 0) { - if ((int)val - (int)left_step < 0) - val = 0; - else - val -= left_step; - - reg &= ~TWL6040_HF_VOL_MASK; - twl6040_write(codec, TWL6040_REG_HFLGAIN, - reg | (0x1D - val)); - } else { - left_complete = 1; - } - } - - /* right channel */ - right_step = (right_step > 0x1D) ? 0x1D : right_step; - reg = twl6040_read_reg_cache(codec, TWL6040_REG_HFRGAIN); - reg = 0x1D - reg; - val = (reg & TWL6040_HF_VOL_MASK); - if (handsfree->ramp == TWL6040_RAMP_UP) { - /* ramp step up */ - if (val < handsfree->right_vol) { - if (val + right_step > handsfree->right_vol) - val = handsfree->right_vol; - else - val += right_step; - - reg &= ~TWL6040_HF_VOL_MASK; - twl6040_write(codec, TWL6040_REG_HFRGAIN, - reg | (0x1D - val)); - } else { - right_complete = 1; - } - } else if (handsfree->ramp == TWL6040_RAMP_DOWN) { - /* ramp step down */ - if (val > 0) { - if ((int)val - (int)right_step < 0) - val = 0; - else - val -= right_step; - - reg &= ~TWL6040_HF_VOL_MASK; - twl6040_write(codec, TWL6040_REG_HFRGAIN, - reg | (0x1D - val)); - } - } - - return left_complete & right_complete; -} - -/* - * This work ramps both output PGAs at stream start/stop time to - * minimise pop associated with DAPM power switching. - */ -static void twl6040_pga_hs_work(struct work_struct *work) -{ - struct twl6040_data *priv = - container_of(work, struct twl6040_data, headset.work.work); - struct snd_soc_codec *codec = priv->codec; - struct twl6040_output *headset = &priv->headset; - int i, headset_complete; - - /* do we need to ramp at all ? */ - if (headset->ramp == TWL6040_RAMP_NONE) - return; - - /* HS PGA gain range: 0x0 - 0xf (0 - 15) */ - for (i = 0; i < 16; i++) { - headset_complete = twl6040_hs_ramp_step(codec, - headset->left_step, - headset->right_step); - - /* ramp finished ? */ - if (headset_complete) - break; - - schedule_timeout_interruptible( - msecs_to_jiffies(headset->step_delay)); - } - - if (headset->ramp == TWL6040_RAMP_DOWN) { - headset->active = 0; - complete(&headset->ramp_done); - } else { - headset->active = 1; - } - headset->ramp = TWL6040_RAMP_NONE; -} - -static void twl6040_pga_hf_work(struct work_struct *work) -{ - struct twl6040_data *priv = - container_of(work, struct twl6040_data, handsfree.work.work); - struct snd_soc_codec *codec = priv->codec; - struct twl6040_output *handsfree = &priv->handsfree; - int i, handsfree_complete; - - /* do we need to ramp at all ? */ - if (handsfree->ramp == TWL6040_RAMP_NONE) - return; - - /* - * HF PGA gain range: 0x00 - 0x1d (0 - 29) */ - for (i = 0; i < 30; i++) { - handsfree_complete = twl6040_hf_ramp_step(codec, - handsfree->left_step, - handsfree->right_step); - - /* ramp finished ? */ - if (handsfree_complete) - break; - - schedule_timeout_interruptible( - msecs_to_jiffies(handsfree->step_delay)); - } - - - if (handsfree->ramp == TWL6040_RAMP_DOWN) { - handsfree->active = 0; - complete(&handsfree->ramp_done); - } else - handsfree->active = 1; - handsfree->ramp = TWL6040_RAMP_NONE; -} - -static int out_drv_event(struct snd_soc_dapm_widget *w, - struct snd_kcontrol *kcontrol, int event) -{ - struct snd_soc_codec *codec = w->codec; - struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec); - struct twl6040_output *out; - struct delayed_work *work; - - switch (w->shift) { - case 2: /* Headset output driver */ - out = &priv->headset; - work = &out->work; - /* - * Make sure, that we do not mess up variables for already - * executing work. - */ - cancel_delayed_work_sync(work); - - out->left_step = priv->hs_left_step; - out->right_step = priv->hs_right_step; - out->step_delay = 5; /* 5 ms between volume ramp steps */ - break; - case 4: /* Handsfree output driver */ - out = &priv->handsfree; - work = &out->work; - /* - * Make sure, that we do not mess up variables for already - * executing work. - */ - cancel_delayed_work_sync(work); - - out->left_step = priv->hf_left_step; - out->right_step = priv->hf_right_step; - out->step_delay = 5; /* 5 ms between volume ramp steps */ - break; - default: - return -1; - } - - switch (event) { - case SND_SOC_DAPM_POST_PMU: - if (out->active) - break; - - /* don't use volume ramp for power-up */ - out->ramp = TWL6040_RAMP_UP; - out->left_step = out->left_vol; - out->right_step = out->right_vol; - - queue_delayed_work(priv->workqueue, work, msecs_to_jiffies(1)); - break; - - case SND_SOC_DAPM_PRE_PMD: - if (!out->active) - break; - - /* use volume ramp for power-down */ - out->ramp = TWL6040_RAMP_DOWN; - INIT_COMPLETION(out->ramp_done); - - queue_delayed_work(priv->workqueue, work, msecs_to_jiffies(1)); - - wait_for_completion_timeout(&out->ramp_done, - msecs_to_jiffies(2000)); - break; - } - - return 0; -} - /* set headset dac and driver power mode */ static int headset_power_mode(struct snd_soc_codec *codec, int high_perf) { @@ -747,71 +410,6 @@ static irqreturn_t twl6040_audio_handler(int irq, void *data) return IRQ_HANDLED; } -static int twl6040_put_volsw(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); - struct twl6040_data *twl6040_priv = snd_soc_codec_get_drvdata(codec); - struct twl6040_output *out = NULL; - struct soc_mixer_control *mc = - (struct soc_mixer_control *)kcontrol->private_value; - int ret; - - /* For HS and HF we shadow the values and only actually write - * them out when active in order to ensure the amplifier comes on - * as quietly as possible. */ - switch (mc->reg) { - case TWL6040_REG_HSGAIN: - out = &twl6040_priv->headset; - break; - case TWL6040_REG_HFLGAIN: - out = &twl6040_priv->handsfree; - break; - default: - dev_warn(codec->dev, "%s: Unexpected register: 0x%02x\n", - __func__, mc->reg); - return -EINVAL; - } - - out->left_vol = ucontrol->value.integer.value[0]; - out->right_vol = ucontrol->value.integer.value[1]; - if (!out->active) - return 1; - - ret = snd_soc_put_volsw(kcontrol, ucontrol); - if (ret < 0) - return ret; - - return 1; -} - -static int twl6040_get_volsw(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); - struct twl6040_data *twl6040_priv = snd_soc_codec_get_drvdata(codec); - struct twl6040_output *out = &twl6040_priv->headset; - struct soc_mixer_control *mc = - (struct soc_mixer_control *)kcontrol->private_value; - - switch (mc->reg) { - case TWL6040_REG_HSGAIN: - out = &twl6040_priv->headset; - break; - case TWL6040_REG_HFLGAIN: - out = &twl6040_priv->handsfree; - break; - default: - dev_warn(codec->dev, "%s: Unexpected register: 0x%02x\n", - __func__, mc->reg); - return -EINVAL; - } - - ucontrol->value.integer.value[0] = out->left_vol; - ucontrol->value.integer.value[1] = out->right_vol; - return 0; -} - static int twl6040_soc_dapm_put_vibra_enum(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { @@ -1076,12 +674,10 @@ static const struct snd_kcontrol_new twl6040_snd_controls[] = { TWL6040_REG_LINEGAIN, 0, 3, 7, 0, afm_amp_tlv), /* Playback gains */ - SOC_DOUBLE_EXT_TLV("Headset Playback Volume", - TWL6040_REG_HSGAIN, 0, 4, 0xF, 1, twl6040_get_volsw, - twl6040_put_volsw, hs_tlv), - SOC_DOUBLE_R_EXT_TLV("Handsfree Playback Volume", - TWL6040_REG_HFLGAIN, TWL6040_REG_HFRGAIN, 0, 0x1D, 1, - twl6040_get_volsw, twl6040_put_volsw, hf_tlv), + SOC_DOUBLE_TLV("Headset Playback Volume", + TWL6040_REG_HSGAIN, 0, 4, 0xF, 1, hs_tlv), + SOC_DOUBLE_R_TLV("Handsfree Playback Volume", + TWL6040_REG_HFLGAIN, TWL6040_REG_HFRGAIN, 0, 0x1D, 1, hf_tlv), SOC_SINGLE_TLV("Earphone Playback Volume", TWL6040_REG_EARCTL, 1, 0xF, 1, ep_tlv), @@ -1180,22 +776,14 @@ static const struct snd_soc_dapm_widget twl6040_dapm_widgets[] = { &auxr_switch_control), /* Analog playback drivers */ - SND_SOC_DAPM_OUT_DRV_E("HF Left Driver", - TWL6040_REG_HFLCTL, 4, 0, NULL, 0, - out_drv_event, - SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), - SND_SOC_DAPM_OUT_DRV_E("HF Right Driver", - TWL6040_REG_HFRCTL, 4, 0, NULL, 0, - out_drv_event, - SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), - SND_SOC_DAPM_OUT_DRV_E("HS Left Driver", - TWL6040_REG_HSLCTL, 2, 0, NULL, 0, - out_drv_event, - SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), - SND_SOC_DAPM_OUT_DRV_E("HS Right Driver", - TWL6040_REG_HSRCTL, 2, 0, NULL, 0, - out_drv_event, - SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_OUT_DRV("HF Left Driver", + TWL6040_REG_HFLCTL, 4, 0, NULL, 0), + SND_SOC_DAPM_OUT_DRV("HF Right Driver", + TWL6040_REG_HFRCTL, 4, 0, NULL, 0), + SND_SOC_DAPM_OUT_DRV("HS Left Driver", + TWL6040_REG_HSLCTL, 2, 0, NULL, 0), + SND_SOC_DAPM_OUT_DRV("HS Right Driver", + TWL6040_REG_HSRCTL, 2, 0, NULL, 0), SND_SOC_DAPM_OUT_DRV_E("Earphone Driver", TWL6040_REG_EARCTL, 0, 0, NULL, 0, twl6040_ep_drv_event, @@ -1339,8 +927,7 @@ static int twl6040_set_bias_level(struct snd_soc_codec *codec, static int twl6040_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_codec *codec = dai->codec; struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec); snd_pcm_hw_constraint_list(substream->runtime, 0, @@ -1354,8 +941,7 @@ static int twl6040_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_codec *codec = dai->codec; struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec); int rate; @@ -1391,8 +977,7 @@ static int twl6040_hw_params(struct snd_pcm_substream *substream, static int twl6040_prepare(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_codec *codec = dai->codec; struct twl6040 *twl6040 = codec->control_data; struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec); int ret; @@ -1570,14 +1155,9 @@ static int twl6040_probe(struct snd_soc_codec *codec) } INIT_DELAYED_WORK(&priv->hs_jack.work, twl6040_accessory_work); - INIT_DELAYED_WORK(&priv->headset.work, twl6040_pga_hs_work); - INIT_DELAYED_WORK(&priv->handsfree.work, twl6040_pga_hf_work); mutex_init(&priv->mutex); - init_completion(&priv->headset.ramp_done); - init_completion(&priv->handsfree.ramp_done); - ret = request_threaded_irq(priv->plug_irq, NULL, twl6040_audio_handler, 0, "twl6040_irq_plug", codec); if (ret) { diff --git a/sound/soc/codecs/uda134x.c b/sound/soc/codecs/uda134x.c index 797b0dde2c6..6c3d43b8ee8 100644 --- a/sound/soc/codecs/uda134x.c +++ b/sound/soc/codecs/uda134x.c @@ -159,8 +159,7 @@ static int uda134x_mute(struct snd_soc_dai *dai, int mute) static int uda134x_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_codec *codec =rtd->codec; + struct snd_soc_codec *codec = dai->codec; struct uda134x_priv *uda134x = snd_soc_codec_get_drvdata(codec); struct snd_pcm_runtime *master_runtime; @@ -191,8 +190,7 @@ static int uda134x_startup(struct snd_pcm_substream *substream, static void uda134x_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_codec *codec = dai->codec; struct uda134x_priv *uda134x = snd_soc_codec_get_drvdata(codec); if (uda134x->master_substream == substream) diff --git a/sound/soc/codecs/uda1380.c b/sound/soc/codecs/uda1380.c index 4f1b23d7e40..2502214b84a 100644 --- a/sound/soc/codecs/uda1380.c +++ b/sound/soc/codecs/uda1380.c @@ -502,8 +502,7 @@ static int uda1380_set_dai_fmt_capture(struct snd_soc_dai *codec_dai, static int uda1380_trigger(struct snd_pcm_substream *substream, int cmd, struct snd_soc_dai *dai) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_codec *codec = dai->codec; struct uda1380_priv *uda1380 = snd_soc_codec_get_drvdata(codec); int mixer = uda1380_read_reg_cache(codec, UDA1380_MIXER); @@ -528,8 +527,7 @@ static int uda1380_pcm_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_codec *codec = dai->codec; u16 clk = uda1380_read_reg_cache(codec, UDA1380_CLK); /* set WSPLL power and divider if running from this clock */ diff --git a/sound/soc/codecs/wl1273.c b/sound/soc/codecs/wl1273.c index 3d868dc4009..7b24d6d192e 100644 --- a/sound/soc/codecs/wl1273.c +++ b/sound/soc/codecs/wl1273.c @@ -293,8 +293,7 @@ static const struct snd_kcontrol_new wl1273_controls[] = { static int wl1273_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_codec *codec = dai->codec; struct wl1273_priv *wl1273 = snd_soc_codec_get_drvdata(codec); switch (wl1273->mode) { @@ -329,8 +328,7 @@ static int wl1273_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct wl1273_priv *wl1273 = snd_soc_codec_get_drvdata(rtd->codec); + struct wl1273_priv *wl1273 = snd_soc_codec_get_drvdata(dai->codec); struct wl1273_core *core = wl1273->core; unsigned int rate, width, r; diff --git a/sound/soc/codecs/wm1250-ev1.c b/sound/soc/codecs/wm1250-ev1.c index aefb4f89be0..e0b51e9f8b1 100644 --- a/sound/soc/codecs/wm1250-ev1.c +++ b/sound/soc/codecs/wm1250-ev1.c @@ -79,22 +79,65 @@ static const struct snd_soc_dapm_route wm1250_ev1_dapm_routes[] = { { "WM1250 Output", NULL, "DAC" }, }; +static int wm1250_ev1_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct wm1250_priv *wm1250 = snd_soc_codec_get_drvdata(dai->codec); + + switch (params_rate(params)) { + case 8000: + gpio_set_value(wm1250->gpios[WM1250_EV1_GPIO_CLK_SEL0].gpio, + 1); + gpio_set_value(wm1250->gpios[WM1250_EV1_GPIO_CLK_SEL1].gpio, + 1); + break; + case 16000: + gpio_set_value(wm1250->gpios[WM1250_EV1_GPIO_CLK_SEL0].gpio, + 0); + gpio_set_value(wm1250->gpios[WM1250_EV1_GPIO_CLK_SEL1].gpio, + 1); + break; + case 32000: + gpio_set_value(wm1250->gpios[WM1250_EV1_GPIO_CLK_SEL0].gpio, + 1); + gpio_set_value(wm1250->gpios[WM1250_EV1_GPIO_CLK_SEL1].gpio, + 0); + break; + case 64000: + gpio_set_value(wm1250->gpios[WM1250_EV1_GPIO_CLK_SEL0].gpio, + 0); + gpio_set_value(wm1250->gpios[WM1250_EV1_GPIO_CLK_SEL1].gpio, + 0); + break; + default: + return -EINVAL; + } + + return 0; +} + +static const struct snd_soc_dai_ops wm1250_ev1_ops = { + .hw_params = wm1250_ev1_hw_params, +}; + static struct snd_soc_dai_driver wm1250_ev1_dai = { .name = "wm1250-ev1", .playback = { .stream_name = "Playback", .channels_min = 1, - .channels_max = 1, + .channels_max = 2, .rates = SNDRV_PCM_RATE_8000, .formats = SNDRV_PCM_FMTBIT_S16_LE, }, .capture = { .stream_name = "Capture", .channels_min = 1, - .channels_max = 1, + .channels_max = 2, .rates = SNDRV_PCM_RATE_8000, .formats = SNDRV_PCM_FMTBIT_S16_LE, }, + .ops = &wm1250_ev1_ops, }; static struct snd_soc_codec_driver soc_codec_dev_wm1250_ev1 = { @@ -215,23 +258,7 @@ static struct i2c_driver wm1250_ev1_i2c_driver = { .id_table = wm1250_ev1_i2c_id, }; -static int __init wm1250_ev1_modinit(void) -{ - int ret = 0; - - ret = i2c_add_driver(&wm1250_ev1_i2c_driver); - if (ret != 0) - pr_err("Failed to register WM1250-EV1 I2C driver: %d\n", ret); - - return ret; -} -module_init(wm1250_ev1_modinit); - -static void __exit wm1250_ev1_exit(void) -{ - i2c_del_driver(&wm1250_ev1_i2c_driver); -} -module_exit(wm1250_ev1_exit); +module_i2c_driver(wm1250_ev1_i2c_driver); MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>"); MODULE_DESCRIPTION("WM1250-EV1 audio I/O module driver"); diff --git a/sound/soc/codecs/wm5100-tables.c b/sound/soc/codecs/wm5100-tables.c index 9a18fae6820..e167207a19c 100644 --- a/sound/soc/codecs/wm5100-tables.c +++ b/sound/soc/codecs/wm5100-tables.c @@ -32,7 +32,18 @@ bool wm5100_volatile_register(struct device *dev, unsigned int reg) case WM5100_MIC_DETECT_3: return 1; default: - return 0; + if ((reg >= WM5100_DSP1_PM_0 && reg <= WM5100_DSP1_PM_1535) || + (reg >= WM5100_DSP1_ZM_0 && reg <= WM5100_DSP1_ZM_2047) || + (reg >= WM5100_DSP1_DM_0 && reg <= WM5100_DSP1_DM_511) || + (reg >= WM5100_DSP2_PM_0 && reg <= WM5100_DSP2_PM_1535) || + (reg >= WM5100_DSP2_ZM_0 && reg <= WM5100_DSP2_ZM_2047) || + (reg >= WM5100_DSP2_DM_0 && reg <= WM5100_DSP2_DM_511) || + (reg >= WM5100_DSP3_PM_0 && reg <= WM5100_DSP3_PM_1535) || + (reg >= WM5100_DSP3_ZM_0 && reg <= WM5100_DSP3_ZM_2047) || + (reg >= WM5100_DSP3_DM_0 && reg <= WM5100_DSP3_DM_511)) + return 1; + else + return 0; } } @@ -697,9 +708,110 @@ bool wm5100_readable_register(struct device *dev, unsigned int reg) case WM5100_HPLPF3_2: case WM5100_HPLPF4_1: case WM5100_HPLPF4_2: + case WM5100_DSP1_CONTROL_1: + case WM5100_DSP1_CONTROL_2: + case WM5100_DSP1_CONTROL_3: + case WM5100_DSP1_CONTROL_4: + case WM5100_DSP1_CONTROL_5: + case WM5100_DSP1_CONTROL_6: + case WM5100_DSP1_CONTROL_7: + case WM5100_DSP1_CONTROL_8: + case WM5100_DSP1_CONTROL_9: + case WM5100_DSP1_CONTROL_10: + case WM5100_DSP1_CONTROL_11: + case WM5100_DSP1_CONTROL_12: + case WM5100_DSP1_CONTROL_13: + case WM5100_DSP1_CONTROL_14: + case WM5100_DSP1_CONTROL_15: + case WM5100_DSP1_CONTROL_16: + case WM5100_DSP1_CONTROL_17: + case WM5100_DSP1_CONTROL_18: + case WM5100_DSP1_CONTROL_19: + case WM5100_DSP1_CONTROL_20: + case WM5100_DSP1_CONTROL_21: + case WM5100_DSP1_CONTROL_22: + case WM5100_DSP1_CONTROL_23: + case WM5100_DSP1_CONTROL_24: + case WM5100_DSP1_CONTROL_25: + case WM5100_DSP1_CONTROL_26: + case WM5100_DSP1_CONTROL_27: + case WM5100_DSP1_CONTROL_28: + case WM5100_DSP1_CONTROL_29: + case WM5100_DSP1_CONTROL_30: + case WM5100_DSP2_CONTROL_1: + case WM5100_DSP2_CONTROL_2: + case WM5100_DSP2_CONTROL_3: + case WM5100_DSP2_CONTROL_4: + case WM5100_DSP2_CONTROL_5: + case WM5100_DSP2_CONTROL_6: + case WM5100_DSP2_CONTROL_7: + case WM5100_DSP2_CONTROL_8: + case WM5100_DSP2_CONTROL_9: + case WM5100_DSP2_CONTROL_10: + case WM5100_DSP2_CONTROL_11: + case WM5100_DSP2_CONTROL_12: + case WM5100_DSP2_CONTROL_13: + case WM5100_DSP2_CONTROL_14: + case WM5100_DSP2_CONTROL_15: + case WM5100_DSP2_CONTROL_16: + case WM5100_DSP2_CONTROL_17: + case WM5100_DSP2_CONTROL_18: + case WM5100_DSP2_CONTROL_19: + case WM5100_DSP2_CONTROL_20: + case WM5100_DSP2_CONTROL_21: + case WM5100_DSP2_CONTROL_22: + case WM5100_DSP2_CONTROL_23: + case WM5100_DSP2_CONTROL_24: + case WM5100_DSP2_CONTROL_25: + case WM5100_DSP2_CONTROL_26: + case WM5100_DSP2_CONTROL_27: + case WM5100_DSP2_CONTROL_28: + case WM5100_DSP2_CONTROL_29: + case WM5100_DSP2_CONTROL_30: + case WM5100_DSP3_CONTROL_1: + case WM5100_DSP3_CONTROL_2: + case WM5100_DSP3_CONTROL_3: + case WM5100_DSP3_CONTROL_4: + case WM5100_DSP3_CONTROL_5: + case WM5100_DSP3_CONTROL_6: + case WM5100_DSP3_CONTROL_7: + case WM5100_DSP3_CONTROL_8: + case WM5100_DSP3_CONTROL_9: + case WM5100_DSP3_CONTROL_10: + case WM5100_DSP3_CONTROL_11: + case WM5100_DSP3_CONTROL_12: + case WM5100_DSP3_CONTROL_13: + case WM5100_DSP3_CONTROL_14: + case WM5100_DSP3_CONTROL_15: + case WM5100_DSP3_CONTROL_16: + case WM5100_DSP3_CONTROL_17: + case WM5100_DSP3_CONTROL_18: + case WM5100_DSP3_CONTROL_19: + case WM5100_DSP3_CONTROL_20: + case WM5100_DSP3_CONTROL_21: + case WM5100_DSP3_CONTROL_22: + case WM5100_DSP3_CONTROL_23: + case WM5100_DSP3_CONTROL_24: + case WM5100_DSP3_CONTROL_25: + case WM5100_DSP3_CONTROL_26: + case WM5100_DSP3_CONTROL_27: + case WM5100_DSP3_CONTROL_28: + case WM5100_DSP3_CONTROL_29: + case WM5100_DSP3_CONTROL_30: return 1; default: - return 0; + if ((reg >= WM5100_DSP1_PM_0 && reg <= WM5100_DSP1_PM_1535) || + (reg >= WM5100_DSP1_ZM_0 && reg <= WM5100_DSP1_ZM_2047) || + (reg >= WM5100_DSP1_DM_0 && reg <= WM5100_DSP1_DM_511) || + (reg >= WM5100_DSP2_PM_0 && reg <= WM5100_DSP2_PM_1535) || + (reg >= WM5100_DSP2_ZM_0 && reg <= WM5100_DSP2_ZM_2047) || + (reg >= WM5100_DSP2_DM_0 && reg <= WM5100_DSP2_DM_511) || + (reg >= WM5100_DSP3_PM_0 && reg <= WM5100_DSP3_PM_1535) || + (reg >= WM5100_DSP3_ZM_0 && reg <= WM5100_DSP3_ZM_2047) || + (reg >= WM5100_DSP3_DM_0 && reg <= WM5100_DSP3_DM_511)) + return 1; + else + return 0; } } @@ -1361,4 +1473,13 @@ struct reg_default wm5100_reg_defaults[WM5100_REGISTER_COUNT] = { { 0x0EC9, 0x0000 }, /* R3785 - HPLPF3_2 */ { 0x0ECC, 0x0000 }, /* R3788 - HPLPF4_1 */ { 0x0ECD, 0x0000 }, /* R3789 - HPLPF4_2 */ + { 0x0F02, 0x0000 }, /* R3842 - DSP1 Control 2 */ + { 0x0F03, 0x0000 }, /* R3843 - DSP1 Control 3 */ + { 0x0F04, 0x0000 }, /* R3844 - DSP1 Control 4 */ + { 0x1002, 0x0000 }, /* R4098 - DSP2 Control 2 */ + { 0x1003, 0x0000 }, /* R4099 - DSP2 Control 3 */ + { 0x1004, 0x0000 }, /* R4100 - DSP2 Control 4 */ + { 0x1102, 0x0000 }, /* R4354 - DSP3 Control 2 */ + { 0x1103, 0x0000 }, /* R4355 - DSP3 Control 3 */ + { 0x1104, 0x0000 }, /* R4356 - DSP3 Control 4 */ }; diff --git a/sound/soc/codecs/wm5100.c b/sound/soc/codecs/wm5100.c index b9c185ce64e..cb6d5372103 100644 --- a/sound/soc/codecs/wm5100.c +++ b/sound/soc/codecs/wm5100.c @@ -1265,29 +1265,12 @@ static const __devinitdata struct reg_default wm5100_reva_patches[] = { { WM5100_AUDIO_IF_3_19, 1 }, }; -static int wm5100_dai_to_base(struct snd_soc_dai *dai) -{ - switch (dai->id) { - case 0: - return WM5100_AUDIO_IF_1_1 - 1; - case 1: - return WM5100_AUDIO_IF_2_1 - 1; - case 2: - return WM5100_AUDIO_IF_3_1 - 1; - default: - BUG(); - return -EINVAL; - } -} - static int wm5100_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) { struct snd_soc_codec *codec = dai->codec; int lrclk, bclk, mask, base; - base = wm5100_dai_to_base(dai); - if (base < 0) - return base; + base = dai->driver->base; lrclk = 0; bclk = 0; @@ -1414,9 +1397,7 @@ static int wm5100_hw_params(struct snd_pcm_substream *substream, int i, base, bclk, aif_rate, lrclk, wl, fl, sr; int *bclk_rates; - base = wm5100_dai_to_base(dai); - if (base < 0) - return base; + base = dai->driver->base; /* Data sizes if not using TDM */ wl = snd_pcm_format_width(params_format(params)); @@ -1897,6 +1878,7 @@ static int wm5100_set_fll(struct snd_soc_codec *codec, int fll_id, int source, static struct snd_soc_dai_driver wm5100_dai[] = { { .name = "wm5100-aif1", + .base = WM5100_AUDIO_IF_1_1 - 1, .playback = { .stream_name = "AIF1 Playback", .channels_min = 2, @@ -1916,6 +1898,7 @@ static struct snd_soc_dai_driver wm5100_dai[] = { { .name = "wm5100-aif2", .id = 1, + .base = WM5100_AUDIO_IF_2_1 - 1, .playback = { .stream_name = "AIF2 Playback", .channels_min = 2, @@ -1935,6 +1918,7 @@ static struct snd_soc_dai_driver wm5100_dai[] = { { .name = "wm5100-aif3", .id = 2, + .base = WM5100_AUDIO_IF_3_1 - 1, .playback = { .stream_name = "AIF3 Playback", .channels_min = 2, @@ -2454,7 +2438,7 @@ static __devinit int wm5100_i2c_probe(struct i2c_client *i2c, wm5100->dev = &i2c->dev; - wm5100->regmap = regmap_init_i2c(i2c, &wm5100_regmap); + wm5100->regmap = devm_regmap_init_i2c(i2c, &wm5100_regmap); if (IS_ERR(wm5100->regmap)) { ret = PTR_ERR(wm5100->regmap); dev_err(&i2c->dev, "Failed to allocate register map: %d\n", @@ -2479,7 +2463,7 @@ static __devinit int wm5100_i2c_probe(struct i2c_client *i2c, if (ret != 0) { dev_err(&i2c->dev, "Failed to request core supplies: %d\n", ret); - goto err_regmap; + goto err; } ret = regulator_bulk_enable(ARRAY_SIZE(wm5100->core_supplies), @@ -2487,7 +2471,7 @@ static __devinit int wm5100_i2c_probe(struct i2c_client *i2c, if (ret != 0) { dev_err(&i2c->dev, "Failed to enable core supplies: %d\n", ret); - goto err_regmap; + goto err; } if (wm5100->pdata.ldo_ena) { @@ -2660,8 +2644,6 @@ err_ldo: err_enable: regulator_bulk_disable(ARRAY_SIZE(wm5100->core_supplies), wm5100->core_supplies); -err_regmap: - regmap_exit(wm5100->regmap); err: return ret; } @@ -2682,7 +2664,6 @@ static __devexit int wm5100_i2c_remove(struct i2c_client *i2c) gpio_set_value_cansleep(wm5100->pdata.ldo_ena, 0); gpio_free(wm5100->pdata.ldo_ena); } - regmap_exit(wm5100->regmap); return 0; } @@ -2749,17 +2730,7 @@ static struct i2c_driver wm5100_i2c_driver = { .id_table = wm5100_i2c_id, }; -static int __init wm5100_modinit(void) -{ - return i2c_add_driver(&wm5100_i2c_driver); -} -module_init(wm5100_modinit); - -static void __exit wm5100_exit(void) -{ - i2c_del_driver(&wm5100_i2c_driver); -} -module_exit(wm5100_exit); +module_i2c_driver(wm5100_i2c_driver); MODULE_DESCRIPTION("ASoC WM5100 driver"); MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>"); diff --git a/sound/soc/codecs/wm5100.h b/sound/soc/codecs/wm5100.h index 25cb6016f9d..935a9b7fb27 100644 --- a/sound/soc/codecs/wm5100.h +++ b/sound/soc/codecs/wm5100.h @@ -709,6 +709,96 @@ int wm5100_detect(struct snd_soc_codec *codec, struct snd_soc_jack *jack); #define WM5100_HPLPF3_2 0xEC9 #define WM5100_HPLPF4_1 0xECC #define WM5100_HPLPF4_2 0xECD +#define WM5100_DSP1_CONTROL_1 0xF00 +#define WM5100_DSP1_CONTROL_2 0xF02 +#define WM5100_DSP1_CONTROL_3 0xF03 +#define WM5100_DSP1_CONTROL_4 0xF04 +#define WM5100_DSP1_CONTROL_5 0xF06 +#define WM5100_DSP1_CONTROL_6 0xF07 +#define WM5100_DSP1_CONTROL_7 0xF08 +#define WM5100_DSP1_CONTROL_8 0xF09 +#define WM5100_DSP1_CONTROL_9 0xF0A +#define WM5100_DSP1_CONTROL_10 0xF0B +#define WM5100_DSP1_CONTROL_11 0xF0C +#define WM5100_DSP1_CONTROL_12 0xF0D +#define WM5100_DSP1_CONTROL_13 0xF0F +#define WM5100_DSP1_CONTROL_14 0xF10 +#define WM5100_DSP1_CONTROL_15 0xF11 +#define WM5100_DSP1_CONTROL_16 0xF12 +#define WM5100_DSP1_CONTROL_17 0xF13 +#define WM5100_DSP1_CONTROL_18 0xF14 +#define WM5100_DSP1_CONTROL_19 0xF16 +#define WM5100_DSP1_CONTROL_20 0xF17 +#define WM5100_DSP1_CONTROL_21 0xF18 +#define WM5100_DSP1_CONTROL_22 0xF1A +#define WM5100_DSP1_CONTROL_23 0xF1B +#define WM5100_DSP1_CONTROL_24 0xF1C +#define WM5100_DSP1_CONTROL_25 0xF1E +#define WM5100_DSP1_CONTROL_26 0xF20 +#define WM5100_DSP1_CONTROL_27 0xF21 +#define WM5100_DSP1_CONTROL_28 0xF22 +#define WM5100_DSP1_CONTROL_29 0xF23 +#define WM5100_DSP1_CONTROL_30 0xF24 +#define WM5100_DSP2_CONTROL_1 0x1000 +#define WM5100_DSP2_CONTROL_2 0x1002 +#define WM5100_DSP2_CONTROL_3 0x1003 +#define WM5100_DSP2_CONTROL_4 0x1004 +#define WM5100_DSP2_CONTROL_5 0x1006 +#define WM5100_DSP2_CONTROL_6 0x1007 +#define WM5100_DSP2_CONTROL_7 0x1008 +#define WM5100_DSP2_CONTROL_8 0x1009 +#define WM5100_DSP2_CONTROL_9 0x100A +#define WM5100_DSP2_CONTROL_10 0x100B +#define WM5100_DSP2_CONTROL_11 0x100C +#define WM5100_DSP2_CONTROL_12 0x100D +#define WM5100_DSP2_CONTROL_13 0x100F +#define WM5100_DSP2_CONTROL_14 0x1010 +#define WM5100_DSP2_CONTROL_15 0x1011 +#define WM5100_DSP2_CONTROL_16 0x1012 +#define WM5100_DSP2_CONTROL_17 0x1013 +#define WM5100_DSP2_CONTROL_18 0x1014 +#define WM5100_DSP2_CONTROL_19 0x1016 +#define WM5100_DSP2_CONTROL_20 0x1017 +#define WM5100_DSP2_CONTROL_21 0x1018 +#define WM5100_DSP2_CONTROL_22 0x101A +#define WM5100_DSP2_CONTROL_23 0x101B +#define WM5100_DSP2_CONTROL_24 0x101C +#define WM5100_DSP2_CONTROL_25 0x101E +#define WM5100_DSP2_CONTROL_26 0x1020 +#define WM5100_DSP2_CONTROL_27 0x1021 +#define WM5100_DSP2_CONTROL_28 0x1022 +#define WM5100_DSP2_CONTROL_29 0x1023 +#define WM5100_DSP2_CONTROL_30 0x1024 +#define WM5100_DSP3_CONTROL_1 0x1100 +#define WM5100_DSP3_CONTROL_2 0x1102 +#define WM5100_DSP3_CONTROL_3 0x1103 +#define WM5100_DSP3_CONTROL_4 0x1104 +#define WM5100_DSP3_CONTROL_5 0x1106 +#define WM5100_DSP3_CONTROL_6 0x1107 +#define WM5100_DSP3_CONTROL_7 0x1108 +#define WM5100_DSP3_CONTROL_8 0x1109 +#define WM5100_DSP3_CONTROL_9 0x110A +#define WM5100_DSP3_CONTROL_10 0x110B +#define WM5100_DSP3_CONTROL_11 0x110C +#define WM5100_DSP3_CONTROL_12 0x110D +#define WM5100_DSP3_CONTROL_13 0x110F +#define WM5100_DSP3_CONTROL_14 0x1110 +#define WM5100_DSP3_CONTROL_15 0x1111 +#define WM5100_DSP3_CONTROL_16 0x1112 +#define WM5100_DSP3_CONTROL_17 0x1113 +#define WM5100_DSP3_CONTROL_18 0x1114 +#define WM5100_DSP3_CONTROL_19 0x1116 +#define WM5100_DSP3_CONTROL_20 0x1117 +#define WM5100_DSP3_CONTROL_21 0x1118 +#define WM5100_DSP3_CONTROL_22 0x111A +#define WM5100_DSP3_CONTROL_23 0x111B +#define WM5100_DSP3_CONTROL_24 0x111C +#define WM5100_DSP3_CONTROL_25 0x111E +#define WM5100_DSP3_CONTROL_26 0x1120 +#define WM5100_DSP3_CONTROL_27 0x1121 +#define WM5100_DSP3_CONTROL_28 0x1122 +#define WM5100_DSP3_CONTROL_29 0x1123 +#define WM5100_DSP3_CONTROL_30 0x1124 #define WM5100_DSP1_DM_0 0x4000 #define WM5100_DSP1_DM_1 0x4001 #define WM5100_DSP1_DM_2 0x4002 @@ -4561,6 +4651,75 @@ int wm5100_detect(struct snd_soc_codec *codec, struct snd_soc_jack *jack); #define WM5100_LHPF4_COEFF_WIDTH 16 /* LHPF4_COEFF - [15:0] */ /* + * R4132 (0x1024) - DSP2 Control 30 + */ +#define WM5100_DSP2_RATE_MASK 0xC000 /* DSP2_RATE - [15:14] */ +#define WM5100_DSP2_RATE_SHIFT 14 /* DSP2_RATE - [15:14] */ +#define WM5100_DSP2_RATE_WIDTH 2 /* DSP2_RATE - [15:14] */ +#define WM5100_DSP2_DBG_CLK_ENA 0x0008 /* DSP2_DBG_CLK_ENA */ +#define WM5100_DSP2_DBG_CLK_ENA_MASK 0x0008 /* DSP2_DBG_CLK_ENA */ +#define WM5100_DSP2_DBG_CLK_ENA_SHIFT 3 /* DSP2_DBG_CLK_ENA */ +#define WM5100_DSP2_DBG_CLK_ENA_WIDTH 1 /* DSP2_DBG_CLK_ENA */ +#define WM5100_DSP2_SYS_ENA 0x0004 /* DSP2_SYS_ENA */ +#define WM5100_DSP2_SYS_ENA_MASK 0x0004 /* DSP2_SYS_ENA */ +#define WM5100_DSP2_SYS_ENA_SHIFT 2 /* DSP2_SYS_ENA */ +#define WM5100_DSP2_SYS_ENA_WIDTH 1 /* DSP2_SYS_ENA */ +#define WM5100_DSP2_CORE_ENA 0x0002 /* DSP2_CORE_ENA */ +#define WM5100_DSP2_CORE_ENA_MASK 0x0002 /* DSP2_CORE_ENA */ +#define WM5100_DSP2_CORE_ENA_SHIFT 1 /* DSP2_CORE_ENA */ +#define WM5100_DSP2_CORE_ENA_WIDTH 1 /* DSP2_CORE_ENA */ +#define WM5100_DSP2_START 0x0001 /* DSP2_START */ +#define WM5100_DSP2_START_MASK 0x0001 /* DSP2_START */ +#define WM5100_DSP2_START_SHIFT 0 /* DSP2_START */ +#define WM5100_DSP2_START_WIDTH 1 /* DSP2_START */ + +/* + * R3876 (0xF24) - DSP1 Control 30 + */ +#define WM5100_DSP1_RATE_MASK 0xC000 /* DSP1_RATE - [15:14] */ +#define WM5100_DSP1_RATE_SHIFT 14 /* DSP1_RATE - [15:14] */ +#define WM5100_DSP1_RATE_WIDTH 2 /* DSP1_RATE - [15:14] */ +#define WM5100_DSP1_DBG_CLK_ENA 0x0008 /* DSP1_DBG_CLK_ENA */ +#define WM5100_DSP1_DBG_CLK_ENA_MASK 0x0008 /* DSP1_DBG_CLK_ENA */ +#define WM5100_DSP1_DBG_CLK_ENA_SHIFT 3 /* DSP1_DBG_CLK_ENA */ +#define WM5100_DSP1_DBG_CLK_ENA_WIDTH 1 /* DSP1_DBG_CLK_ENA */ +#define WM5100_DSP1_SYS_ENA 0x0004 /* DSP1_SYS_ENA */ +#define WM5100_DSP1_SYS_ENA_MASK 0x0004 /* DSP1_SYS_ENA */ +#define WM5100_DSP1_SYS_ENA_SHIFT 2 /* DSP1_SYS_ENA */ +#define WM5100_DSP1_SYS_ENA_WIDTH 1 /* DSP1_SYS_ENA */ +#define WM5100_DSP1_CORE_ENA 0x0002 /* DSP1_CORE_ENA */ +#define WM5100_DSP1_CORE_ENA_MASK 0x0002 /* DSP1_CORE_ENA */ +#define WM5100_DSP1_CORE_ENA_SHIFT 1 /* DSP1_CORE_ENA */ +#define WM5100_DSP1_CORE_ENA_WIDTH 1 /* DSP1_CORE_ENA */ +#define WM5100_DSP1_START 0x0001 /* DSP1_START */ +#define WM5100_DSP1_START_MASK 0x0001 /* DSP1_START */ +#define WM5100_DSP1_START_SHIFT 0 /* DSP1_START */ +#define WM5100_DSP1_START_WIDTH 1 /* DSP1_START */ + +/* + * R4388 (0x1124) - DSP3 Control 30 + */ +#define WM5100_DSP3_RATE_MASK 0xC000 /* DSP3_RATE - [15:14] */ +#define WM5100_DSP3_RATE_SHIFT 14 /* DSP3_RATE - [15:14] */ +#define WM5100_DSP3_RATE_WIDTH 2 /* DSP3_RATE - [15:14] */ +#define WM5100_DSP3_DBG_CLK_ENA 0x0008 /* DSP3_DBG_CLK_ENA */ +#define WM5100_DSP3_DBG_CLK_ENA_MASK 0x0008 /* DSP3_DBG_CLK_ENA */ +#define WM5100_DSP3_DBG_CLK_ENA_SHIFT 3 /* DSP3_DBG_CLK_ENA */ +#define WM5100_DSP3_DBG_CLK_ENA_WIDTH 1 /* DSP3_DBG_CLK_ENA */ +#define WM5100_DSP3_SYS_ENA 0x0004 /* DSP3_SYS_ENA */ +#define WM5100_DSP3_SYS_ENA_MASK 0x0004 /* DSP3_SYS_ENA */ +#define WM5100_DSP3_SYS_ENA_SHIFT 2 /* DSP3_SYS_ENA */ +#define WM5100_DSP3_SYS_ENA_WIDTH 1 /* DSP3_SYS_ENA */ +#define WM5100_DSP3_CORE_ENA 0x0002 /* DSP3_CORE_ENA */ +#define WM5100_DSP3_CORE_ENA_MASK 0x0002 /* DSP3_CORE_ENA */ +#define WM5100_DSP3_CORE_ENA_SHIFT 1 /* DSP3_CORE_ENA */ +#define WM5100_DSP3_CORE_ENA_WIDTH 1 /* DSP3_CORE_ENA */ +#define WM5100_DSP3_START 0x0001 /* DSP3_START */ +#define WM5100_DSP3_START_MASK 0x0001 /* DSP3_START */ +#define WM5100_DSP3_START_SHIFT 0 /* DSP3_START */ +#define WM5100_DSP3_START_WIDTH 1 /* DSP3_START */ + +/* * R16384 (0x4000) - DSP1 DM 0 */ #define WM5100_DSP1_DM_START_1_MASK 0x00FF /* DSP1_DM_START - [7:0] */ diff --git a/sound/soc/codecs/wm8350.c b/sound/soc/codecs/wm8350.c index aa12c6b6bee..555ee146ae0 100644 --- a/sound/soc/codecs/wm8350.c +++ b/sound/soc/codecs/wm8350.c @@ -71,13 +71,6 @@ struct wm8350_data { int fll_freq_in; }; -static unsigned int wm8350_codec_cache_read(struct snd_soc_codec *codec, - unsigned int reg) -{ - struct wm8350 *wm8350 = codec->control_data; - return wm8350->reg_cache[reg]; -} - static unsigned int wm8350_codec_read(struct snd_soc_codec *codec, unsigned int reg) { @@ -99,7 +92,7 @@ static inline int wm8350_out1_ramp_step(struct snd_soc_codec *codec) { struct wm8350_data *wm8350_data = snd_soc_codec_get_drvdata(codec); struct wm8350_output *out1 = &wm8350_data->out1; - struct wm8350 *wm8350 = codec->control_data; + struct wm8350 *wm8350 = wm8350_data->wm8350; int left_complete = 0, right_complete = 0; u16 reg, val; @@ -165,7 +158,7 @@ static inline int wm8350_out2_ramp_step(struct snd_soc_codec *codec) { struct wm8350_data *wm8350_data = snd_soc_codec_get_drvdata(codec); struct wm8350_output *out2 = &wm8350_data->out2; - struct wm8350 *wm8350 = codec->control_data; + struct wm8350 *wm8350 = wm8350_data->wm8350; int left_complete = 0, right_complete = 0; u16 reg, val; @@ -360,8 +353,8 @@ static int wm8350_put_volsw_2r_vu(struct snd_kcontrol *kcontrol, return ret; /* now hit the volume update bits (always bit 8) */ - val = wm8350_codec_read(codec, reg); - wm8350_codec_write(codec, reg, val | WM8350_OUT1_VU); + val = snd_soc_read(codec, reg); + snd_soc_write(codec, reg, val | WM8350_OUT1_VU); return 1; } @@ -781,7 +774,8 @@ static int wm8350_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 wm8350 *wm8350 = codec->control_data; + struct wm8350_data *wm8350_data = snd_soc_codec_get_drvdata(codec); + struct wm8350 *wm8350 = wm8350_data->wm8350; u16 fll_4; switch (clk_id) { @@ -795,9 +789,9 @@ static int wm8350_set_dai_sysclk(struct snd_soc_dai *codec_dai, case WM8350_MCLK_SEL_PLL_32K: wm8350_set_bits(wm8350, WM8350_CLOCK_CONTROL_1, WM8350_MCLK_SEL); - fll_4 = wm8350_codec_read(codec, WM8350_FLL_CONTROL_4) & + fll_4 = snd_soc_read(codec, WM8350_FLL_CONTROL_4) & ~WM8350_FLL_CLK_SRC_MASK; - wm8350_codec_write(codec, WM8350_FLL_CONTROL_4, fll_4 | clk_id); + snd_soc_write(codec, WM8350_FLL_CONTROL_4, fll_4 | clk_id); break; } @@ -819,39 +813,39 @@ static int wm8350_set_clkdiv(struct snd_soc_dai *codec_dai, int div_id, int div) switch (div_id) { case WM8350_ADC_CLKDIV: - val = wm8350_codec_read(codec, WM8350_ADC_DIVIDER) & + val = snd_soc_read(codec, WM8350_ADC_DIVIDER) & ~WM8350_ADC_CLKDIV_MASK; - wm8350_codec_write(codec, WM8350_ADC_DIVIDER, val | div); + snd_soc_write(codec, WM8350_ADC_DIVIDER, val | div); break; case WM8350_DAC_CLKDIV: - val = wm8350_codec_read(codec, WM8350_DAC_CLOCK_CONTROL) & + val = snd_soc_read(codec, WM8350_DAC_CLOCK_CONTROL) & ~WM8350_DAC_CLKDIV_MASK; - wm8350_codec_write(codec, WM8350_DAC_CLOCK_CONTROL, val | div); + snd_soc_write(codec, WM8350_DAC_CLOCK_CONTROL, val | div); break; case WM8350_BCLK_CLKDIV: - val = wm8350_codec_read(codec, WM8350_CLOCK_CONTROL_1) & + val = snd_soc_read(codec, WM8350_CLOCK_CONTROL_1) & ~WM8350_BCLK_DIV_MASK; - wm8350_codec_write(codec, WM8350_CLOCK_CONTROL_1, val | div); + snd_soc_write(codec, WM8350_CLOCK_CONTROL_1, val | div); break; case WM8350_OPCLK_CLKDIV: - val = wm8350_codec_read(codec, WM8350_CLOCK_CONTROL_1) & + val = snd_soc_read(codec, WM8350_CLOCK_CONTROL_1) & ~WM8350_OPCLK_DIV_MASK; - wm8350_codec_write(codec, WM8350_CLOCK_CONTROL_1, val | div); + snd_soc_write(codec, WM8350_CLOCK_CONTROL_1, val | div); break; case WM8350_SYS_CLKDIV: - val = wm8350_codec_read(codec, WM8350_CLOCK_CONTROL_1) & + val = snd_soc_read(codec, WM8350_CLOCK_CONTROL_1) & ~WM8350_MCLK_DIV_MASK; - wm8350_codec_write(codec, WM8350_CLOCK_CONTROL_1, val | div); + snd_soc_write(codec, WM8350_CLOCK_CONTROL_1, val | div); break; case WM8350_DACLR_CLKDIV: - val = wm8350_codec_read(codec, WM8350_DAC_LR_RATE) & + val = snd_soc_read(codec, WM8350_DAC_LR_RATE) & ~WM8350_DACLRC_RATE_MASK; - wm8350_codec_write(codec, WM8350_DAC_LR_RATE, val | div); + snd_soc_write(codec, WM8350_DAC_LR_RATE, val | div); break; case WM8350_ADCLR_CLKDIV: - val = wm8350_codec_read(codec, WM8350_ADC_LR_RATE) & + val = snd_soc_read(codec, WM8350_ADC_LR_RATE) & ~WM8350_ADCLRC_RATE_MASK; - wm8350_codec_write(codec, WM8350_ADC_LR_RATE, val | div); + snd_soc_write(codec, WM8350_ADC_LR_RATE, val | div); break; default: return -EINVAL; @@ -863,13 +857,13 @@ static int wm8350_set_clkdiv(struct snd_soc_dai *codec_dai, int div_id, int div) static int wm8350_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) { struct snd_soc_codec *codec = codec_dai->codec; - u16 iface = wm8350_codec_read(codec, WM8350_AI_FORMATING) & + u16 iface = snd_soc_read(codec, WM8350_AI_FORMATING) & ~(WM8350_AIF_BCLK_INV | WM8350_AIF_LRCLK_INV | WM8350_AIF_FMT_MASK); - u16 master = wm8350_codec_read(codec, WM8350_AI_DAC_CONTROL) & + u16 master = snd_soc_read(codec, WM8350_AI_DAC_CONTROL) & ~WM8350_BCLK_MSTR; - u16 dac_lrc = wm8350_codec_read(codec, WM8350_DAC_LR_RATE) & + u16 dac_lrc = snd_soc_read(codec, WM8350_DAC_LR_RATE) & ~WM8350_DACLRC_ENA; - u16 adc_lrc = wm8350_codec_read(codec, WM8350_ADC_LR_RATE) & + u16 adc_lrc = snd_soc_read(codec, WM8350_ADC_LR_RATE) & ~WM8350_ADCLRC_ENA; /* set master/slave audio interface */ @@ -922,42 +916,10 @@ static int wm8350_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) return -EINVAL; } - wm8350_codec_write(codec, WM8350_AI_FORMATING, iface); - wm8350_codec_write(codec, WM8350_AI_DAC_CONTROL, master); - wm8350_codec_write(codec, WM8350_DAC_LR_RATE, dac_lrc); - wm8350_codec_write(codec, WM8350_ADC_LR_RATE, adc_lrc); - return 0; -} - -static int wm8350_pcm_trigger(struct snd_pcm_substream *substream, - int cmd, struct snd_soc_dai *codec_dai) -{ - struct snd_soc_codec *codec = codec_dai->codec; - int master = wm8350_codec_cache_read(codec, WM8350_AI_DAC_CONTROL) & - WM8350_BCLK_MSTR; - int enabled = 0; - - /* Check that the DACs or ADCs are enabled since they are - * required for LRC in master mode. The DACs or ADCs need a - * valid audio path i.e. pin -> ADC or DAC -> pin before - * the LRC will be enabled in master mode. */ - if (!master || cmd != SNDRV_PCM_TRIGGER_START) - return 0; - - if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { - enabled = wm8350_codec_cache_read(codec, WM8350_POWER_MGMT_4) & - (WM8350_ADCR_ENA | WM8350_ADCL_ENA); - } else { - enabled = wm8350_codec_cache_read(codec, WM8350_POWER_MGMT_4) & - (WM8350_DACR_ENA | WM8350_DACL_ENA); - } - - if (!enabled) { - dev_err(codec->dev, - "%s: invalid audio path - no clocks available\n", - __func__); - return -EINVAL; - } + snd_soc_write(codec, WM8350_AI_FORMATING, iface); + snd_soc_write(codec, WM8350_AI_DAC_CONTROL, master); + snd_soc_write(codec, WM8350_DAC_LR_RATE, dac_lrc); + snd_soc_write(codec, WM8350_ADC_LR_RATE, adc_lrc); return 0; } @@ -966,8 +928,9 @@ static int wm8350_pcm_hw_params(struct snd_pcm_substream *substream, struct snd_soc_dai *codec_dai) { struct snd_soc_codec *codec = codec_dai->codec; - struct wm8350 *wm8350 = codec->control_data; - u16 iface = wm8350_codec_read(codec, WM8350_AI_FORMATING) & + struct wm8350_data *wm8350_data = snd_soc_codec_get_drvdata(codec); + struct wm8350 *wm8350 = wm8350_data->wm8350; + u16 iface = snd_soc_read(codec, WM8350_AI_FORMATING) & ~WM8350_AIF_WL_MASK; /* bit size */ @@ -985,7 +948,7 @@ static int wm8350_pcm_hw_params(struct snd_pcm_substream *substream, break; } - wm8350_codec_write(codec, WM8350_AI_FORMATING, iface); + snd_soc_write(codec, WM8350_AI_FORMATING, iface); /* The sloping stopband filter is recommended for use with * lower sample rates to improve performance. @@ -1005,12 +968,15 @@ static int wm8350_pcm_hw_params(struct snd_pcm_substream *substream, static int wm8350_mute(struct snd_soc_dai *dai, int mute) { struct snd_soc_codec *codec = dai->codec; - struct wm8350 *wm8350 = codec->control_data; + unsigned int val; if (mute) - wm8350_set_bits(wm8350, WM8350_DAC_MUTE, WM8350_DAC_MUTE_ENA); + val = WM8350_DAC_MUTE_ENA; else - wm8350_clear_bits(wm8350, WM8350_DAC_MUTE, WM8350_DAC_MUTE_ENA); + val = 0; + + snd_soc_update_bits(codec, WM8350_DAC_MUTE, WM8350_DAC_MUTE_ENA, val); + return 0; } @@ -1079,8 +1045,8 @@ static int wm8350_set_fll(struct snd_soc_dai *codec_dai, unsigned int freq_out) { struct snd_soc_codec *codec = codec_dai->codec; - struct wm8350 *wm8350 = codec->control_data; struct wm8350_data *priv = snd_soc_codec_get_drvdata(codec); + struct wm8350 *wm8350 = priv->wm8350; struct _fll_div fll_div; int ret = 0; u16 fll_1, fll_4; @@ -1104,17 +1070,17 @@ static int wm8350_set_fll(struct snd_soc_dai *codec_dai, fll_div.ratio); /* set up N.K & dividers */ - fll_1 = wm8350_codec_read(codec, WM8350_FLL_CONTROL_1) & + fll_1 = snd_soc_read(codec, WM8350_FLL_CONTROL_1) & ~(WM8350_FLL_OUTDIV_MASK | WM8350_FLL_RSP_RATE_MASK | 0xc000); - wm8350_codec_write(codec, WM8350_FLL_CONTROL_1, + snd_soc_write(codec, WM8350_FLL_CONTROL_1, fll_1 | (fll_div.div << 8) | 0x50); - wm8350_codec_write(codec, WM8350_FLL_CONTROL_2, + snd_soc_write(codec, WM8350_FLL_CONTROL_2, (fll_div.ratio << 11) | (fll_div. n & WM8350_FLL_N_MASK)); - wm8350_codec_write(codec, WM8350_FLL_CONTROL_3, fll_div.k); - fll_4 = wm8350_codec_read(codec, WM8350_FLL_CONTROL_4) & + snd_soc_write(codec, WM8350_FLL_CONTROL_3, fll_div.k); + fll_4 = snd_soc_read(codec, WM8350_FLL_CONTROL_4) & ~(WM8350_FLL_FRAC | WM8350_FLL_SLOW_LOCK_REF); - wm8350_codec_write(codec, WM8350_FLL_CONTROL_4, + snd_soc_write(codec, WM8350_FLL_CONTROL_4, fll_4 | (fll_div.k ? WM8350_FLL_FRAC : 0) | (fll_div.ratio == 8 ? WM8350_FLL_SLOW_LOCK_REF : 0)); @@ -1131,8 +1097,8 @@ static int wm8350_set_fll(struct snd_soc_dai *codec_dai, static int wm8350_set_bias_level(struct snd_soc_codec *codec, enum snd_soc_bias_level level) { - struct wm8350 *wm8350 = codec->control_data; struct wm8350_data *priv = snd_soc_codec_get_drvdata(codec); + struct wm8350 *wm8350 = priv->wm8350; struct wm8350_audio_platform_data *platform = wm8350->codec.platform_data; u16 pm1; @@ -1339,35 +1305,36 @@ static void wm8350_hpr_work(struct work_struct *work) wm8350_hp_work(priv, &priv->hpr, WM8350_JACK_R_LVL); } -static irqreturn_t wm8350_hp_jack_handler(int irq, void *data) +static irqreturn_t wm8350_hpl_jack_handler(int irq, void *data) { struct wm8350_data *priv = data; struct wm8350 *wm8350 = priv->wm8350; - struct wm8350_jack_data *jack = NULL; - switch (irq - wm8350->irq_base) { - case WM8350_IRQ_CODEC_JCK_DET_L: #ifndef CONFIG_SND_SOC_WM8350_MODULE - trace_snd_soc_jack_irq("WM8350 HPL"); + trace_snd_soc_jack_irq("WM8350 HPL"); #endif - jack = &priv->hpl; - break; - case WM8350_IRQ_CODEC_JCK_DET_R: + if (device_may_wakeup(wm8350->dev)) + pm_wakeup_event(wm8350->dev, 250); + + schedule_delayed_work(&priv->hpl.work, 200); + + return IRQ_HANDLED; +} + +static irqreturn_t wm8350_hpr_jack_handler(int irq, void *data) +{ + struct wm8350_data *priv = data; + struct wm8350 *wm8350 = priv->wm8350; + #ifndef CONFIG_SND_SOC_WM8350_MODULE - trace_snd_soc_jack_irq("WM8350 HPR"); + trace_snd_soc_jack_irq("WM8350 HPR"); #endif - jack = &priv->hpr; - break; - - default: - BUG(); - } if (device_may_wakeup(wm8350->dev)) pm_wakeup_event(wm8350->dev, 250); - schedule_delayed_work(&jack->work, 200); + schedule_delayed_work(&priv->hpr.work, 200); return IRQ_HANDLED; } @@ -1387,7 +1354,7 @@ int wm8350_hp_jack_detect(struct snd_soc_codec *codec, enum wm8350_jack which, struct snd_soc_jack *jack, int report) { struct wm8350_data *priv = snd_soc_codec_get_drvdata(codec); - struct wm8350 *wm8350 = codec->control_data; + struct wm8350 *wm8350 = priv->wm8350; int irq; int ena; @@ -1418,7 +1385,14 @@ int wm8350_hp_jack_detect(struct snd_soc_codec *codec, enum wm8350_jack which, } /* Sync status */ - wm8350_hp_jack_handler(irq + wm8350->irq_base, priv); + switch (which) { + case WM8350_JDL: + wm8350_hpl_jack_handler(0, priv); + break; + case WM8350_JDR: + wm8350_hpr_jack_handler(0, priv); + break; + } return 0; } @@ -1463,7 +1437,7 @@ int wm8350_mic_jack_detect(struct snd_soc_codec *codec, int detect_report, int short_report) { struct wm8350_data *priv = snd_soc_codec_get_drvdata(codec); - struct wm8350 *wm8350 = codec->control_data; + struct wm8350 *wm8350 = priv->wm8350; priv->mic.jack = jack; priv->mic.report = detect_report; @@ -1491,7 +1465,6 @@ EXPORT_SYMBOL_GPL(wm8350_mic_jack_detect); static const struct snd_soc_dai_ops wm8350_dai_ops = { .hw_params = wm8350_pcm_hw_params, .digital_mute = wm8350_mute, - .trigger = wm8350_pcm_trigger, .set_fmt = wm8350_set_dai_fmt, .set_sysclk = wm8350_set_dai_sysclk, .set_pll = wm8350_set_fll, @@ -1559,9 +1532,9 @@ static int wm8350_codec_probe(struct snd_soc_codec *codec) wm8350_set_bits(wm8350, WM8350_POWER_MGMT_5, WM8350_CODEC_ENA); /* Enable robust clocking mode in ADC */ - wm8350_codec_write(codec, WM8350_SECURITY, 0xa7); - wm8350_codec_write(codec, 0xde, 0x13); - wm8350_codec_write(codec, WM8350_SECURITY, 0); + snd_soc_write(codec, WM8350_SECURITY, 0xa7); + snd_soc_write(codec, 0xde, 0x13); + snd_soc_write(codec, WM8350_SECURITY, 0); /* read OUT1 & OUT2 volumes */ out1 = &priv->out1; @@ -1601,10 +1574,10 @@ static int wm8350_codec_probe(struct snd_soc_codec *codec) WM8350_JDL_ENA | WM8350_JDR_ENA); wm8350_register_irq(wm8350, WM8350_IRQ_CODEC_JCK_DET_L, - wm8350_hp_jack_handler, 0, "Left jack detect", + wm8350_hpl_jack_handler, 0, "Left jack detect", priv); wm8350_register_irq(wm8350, WM8350_IRQ_CODEC_JCK_DET_R, - wm8350_hp_jack_handler, 0, "Right jack detect", + wm8350_hpr_jack_handler, 0, "Right jack detect", priv); wm8350_register_irq(wm8350, WM8350_IRQ_CODEC_MICSCD, wm8350_mic_handler, 0, "Microphone short", priv); diff --git a/sound/soc/codecs/wm8400.c b/sound/soc/codecs/wm8400.c index 898979d2301..5dc31ebcd0e 100644 --- a/sound/soc/codecs/wm8400.c +++ b/sound/soc/codecs/wm8400.c @@ -138,8 +138,8 @@ static int wm8400_outpga_put_volsw_vu(struct snd_kcontrol *kcontrol, return ret; /* now hit the volume update bits (always bit 8) */ - val = wm8400_read(codec, reg); - return wm8400_write(codec, reg, val | 0x0100); + val = snd_soc_read(codec, reg); + return snd_soc_write(codec, reg, val | 0x0100); } #define WM8400_OUTPGA_SINGLE_R_TLV(xname, reg, shift, max, invert, tlv_array) \ @@ -362,8 +362,8 @@ static int inmixer_event (struct snd_soc_dapm_widget *w, { u16 reg, fakepower; - reg = wm8400_read(w->codec, WM8400_POWER_MANAGEMENT_2); - fakepower = wm8400_read(w->codec, WM8400_INTDRIVBITS); + reg = snd_soc_read(w->codec, WM8400_POWER_MANAGEMENT_2); + fakepower = snd_soc_read(w->codec, WM8400_INTDRIVBITS); if (fakepower & ((1 << WM8400_INMIXL_PWR) | (1 << WM8400_AINLMUX_PWR))) { @@ -378,7 +378,7 @@ static int inmixer_event (struct snd_soc_dapm_widget *w, } else { reg &= ~WM8400_AINR_ENA; } - wm8400_write(w->codec, WM8400_POWER_MANAGEMENT_2, reg); + snd_soc_write(w->codec, WM8400_POWER_MANAGEMENT_2, reg); return 0; } @@ -394,7 +394,7 @@ static int outmixer_event (struct snd_soc_dapm_widget *w, switch (reg_shift) { case WM8400_SPEAKER_MIXER | (WM8400_LDSPK << 8) : - reg = wm8400_read(w->codec, WM8400_OUTPUT_MIXER1); + reg = snd_soc_read(w->codec, WM8400_OUTPUT_MIXER1); if (reg & WM8400_LDLO) { printk(KERN_WARNING "Cannot set as Output Mixer 1 LDLO Set\n"); @@ -402,7 +402,7 @@ static int outmixer_event (struct snd_soc_dapm_widget *w, } break; case WM8400_SPEAKER_MIXER | (WM8400_RDSPK << 8): - reg = wm8400_read(w->codec, WM8400_OUTPUT_MIXER2); + reg = snd_soc_read(w->codec, WM8400_OUTPUT_MIXER2); if (reg & WM8400_RDRO) { printk(KERN_WARNING "Cannot set as Output Mixer 2 RDRO Set\n"); @@ -410,7 +410,7 @@ static int outmixer_event (struct snd_soc_dapm_widget *w, } break; case WM8400_OUTPUT_MIXER1 | (WM8400_LDLO << 8): - reg = wm8400_read(w->codec, WM8400_SPEAKER_MIXER); + reg = snd_soc_read(w->codec, WM8400_SPEAKER_MIXER); if (reg & WM8400_LDSPK) { printk(KERN_WARNING "Cannot set as Speaker Mixer LDSPK Set\n"); @@ -418,7 +418,7 @@ static int outmixer_event (struct snd_soc_dapm_widget *w, } break; case WM8400_OUTPUT_MIXER2 | (WM8400_RDRO << 8): - reg = wm8400_read(w->codec, WM8400_SPEAKER_MIXER); + reg = snd_soc_read(w->codec, WM8400_SPEAKER_MIXER); if (reg & WM8400_RDSPK) { printk(KERN_WARNING "Cannot set as Speaker Mixer RDSPK Set\n"); @@ -1021,13 +1021,13 @@ static int wm8400_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id, wm8400->fll_in = freq_in; /* We *must* disable the FLL before any changes */ - reg = wm8400_read(codec, WM8400_POWER_MANAGEMENT_2); + reg = snd_soc_read(codec, WM8400_POWER_MANAGEMENT_2); reg &= ~WM8400_FLL_ENA; - wm8400_write(codec, WM8400_POWER_MANAGEMENT_2, reg); + snd_soc_write(codec, WM8400_POWER_MANAGEMENT_2, reg); - reg = wm8400_read(codec, WM8400_FLL_CONTROL_1); + reg = snd_soc_read(codec, WM8400_FLL_CONTROL_1); reg &= ~WM8400_FLL_OSC_ENA; - wm8400_write(codec, WM8400_FLL_CONTROL_1, reg); + snd_soc_write(codec, WM8400_FLL_CONTROL_1, reg); if (!freq_out) return 0; @@ -1035,15 +1035,15 @@ static int wm8400_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id, reg &= ~(WM8400_FLL_REF_FREQ | WM8400_FLL_FRATIO_MASK); reg |= WM8400_FLL_FRAC | factors.fratio; reg |= factors.freq_ref << WM8400_FLL_REF_FREQ_SHIFT; - wm8400_write(codec, WM8400_FLL_CONTROL_1, reg); + snd_soc_write(codec, WM8400_FLL_CONTROL_1, reg); - wm8400_write(codec, WM8400_FLL_CONTROL_2, factors.k); - wm8400_write(codec, WM8400_FLL_CONTROL_3, factors.n); + snd_soc_write(codec, WM8400_FLL_CONTROL_2, factors.k); + snd_soc_write(codec, WM8400_FLL_CONTROL_3, factors.n); - reg = wm8400_read(codec, WM8400_FLL_CONTROL_4); + reg = snd_soc_read(codec, WM8400_FLL_CONTROL_4); reg &= ~WM8400_FLL_OUTDIV_MASK; reg |= factors.outdiv; - wm8400_write(codec, WM8400_FLL_CONTROL_4, reg); + snd_soc_write(codec, WM8400_FLL_CONTROL_4, reg); return 0; } @@ -1057,8 +1057,8 @@ static int wm8400_set_dai_fmt(struct snd_soc_dai *codec_dai, struct snd_soc_codec *codec = codec_dai->codec; u16 audio1, audio3; - audio1 = wm8400_read(codec, WM8400_AUDIO_INTERFACE_1); - audio3 = wm8400_read(codec, WM8400_AUDIO_INTERFACE_3); + audio1 = snd_soc_read(codec, WM8400_AUDIO_INTERFACE_1); + audio3 = snd_soc_read(codec, WM8400_AUDIO_INTERFACE_3); /* set master/slave audio interface */ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { @@ -1099,8 +1099,8 @@ static int wm8400_set_dai_fmt(struct snd_soc_dai *codec_dai, return -EINVAL; } - wm8400_write(codec, WM8400_AUDIO_INTERFACE_1, audio1); - wm8400_write(codec, WM8400_AUDIO_INTERFACE_3, audio3); + snd_soc_write(codec, WM8400_AUDIO_INTERFACE_1, audio1); + snd_soc_write(codec, WM8400_AUDIO_INTERFACE_3, audio3); return 0; } @@ -1112,24 +1112,24 @@ static int wm8400_set_dai_clkdiv(struct snd_soc_dai *codec_dai, switch (div_id) { case WM8400_MCLK_DIV: - reg = wm8400_read(codec, WM8400_CLOCKING_2) & + reg = snd_soc_read(codec, WM8400_CLOCKING_2) & ~WM8400_MCLK_DIV_MASK; - wm8400_write(codec, WM8400_CLOCKING_2, reg | div); + snd_soc_write(codec, WM8400_CLOCKING_2, reg | div); break; case WM8400_DACCLK_DIV: - reg = wm8400_read(codec, WM8400_CLOCKING_2) & + reg = snd_soc_read(codec, WM8400_CLOCKING_2) & ~WM8400_DAC_CLKDIV_MASK; - wm8400_write(codec, WM8400_CLOCKING_2, reg | div); + snd_soc_write(codec, WM8400_CLOCKING_2, reg | div); break; case WM8400_ADCCLK_DIV: - reg = wm8400_read(codec, WM8400_CLOCKING_2) & + reg = snd_soc_read(codec, WM8400_CLOCKING_2) & ~WM8400_ADC_CLKDIV_MASK; - wm8400_write(codec, WM8400_CLOCKING_2, reg | div); + snd_soc_write(codec, WM8400_CLOCKING_2, reg | div); break; case WM8400_BCLK_DIV: - reg = wm8400_read(codec, WM8400_CLOCKING_1) & + reg = snd_soc_read(codec, WM8400_CLOCKING_1) & ~WM8400_BCLK_DIV_MASK; - wm8400_write(codec, WM8400_CLOCKING_1, reg | div); + snd_soc_write(codec, WM8400_CLOCKING_1, reg | div); break; default: return -EINVAL; @@ -1145,9 +1145,8 @@ static int wm8400_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_codec *codec = rtd->codec; - u16 audio1 = wm8400_read(codec, WM8400_AUDIO_INTERFACE_1); + struct snd_soc_codec *codec = dai->codec; + u16 audio1 = snd_soc_read(codec, WM8400_AUDIO_INTERFACE_1); audio1 &= ~WM8400_AIF_WL_MASK; /* bit size */ @@ -1165,19 +1164,19 @@ static int wm8400_hw_params(struct snd_pcm_substream *substream, break; } - wm8400_write(codec, WM8400_AUDIO_INTERFACE_1, audio1); + snd_soc_write(codec, WM8400_AUDIO_INTERFACE_1, audio1); return 0; } static int wm8400_mute(struct snd_soc_dai *dai, int mute) { struct snd_soc_codec *codec = dai->codec; - u16 val = wm8400_read(codec, WM8400_DAC_CTRL) & ~WM8400_DAC_MUTE; + u16 val = snd_soc_read(codec, WM8400_DAC_CTRL) & ~WM8400_DAC_MUTE; if (mute) - wm8400_write(codec, WM8400_DAC_CTRL, val | WM8400_DAC_MUTE); + snd_soc_write(codec, WM8400_DAC_CTRL, val | WM8400_DAC_MUTE); else - wm8400_write(codec, WM8400_DAC_CTRL, val); + snd_soc_write(codec, WM8400_DAC_CTRL, val); return 0; } @@ -1196,9 +1195,9 @@ static int wm8400_set_bias_level(struct snd_soc_codec *codec, case SND_SOC_BIAS_PREPARE: /* VMID=2*50k */ - val = wm8400_read(codec, WM8400_POWER_MANAGEMENT_1) & + val = snd_soc_read(codec, WM8400_POWER_MANAGEMENT_1) & ~WM8400_VMID_MODE_MASK; - wm8400_write(codec, WM8400_POWER_MANAGEMENT_1, val | 0x2); + snd_soc_write(codec, WM8400_POWER_MANAGEMENT_1, val | 0x2); break; case SND_SOC_BIAS_STANDBY: @@ -1212,74 +1211,74 @@ static int wm8400_set_bias_level(struct snd_soc_codec *codec, return ret; } - wm8400_write(codec, WM8400_POWER_MANAGEMENT_1, + snd_soc_write(codec, WM8400_POWER_MANAGEMENT_1, WM8400_CODEC_ENA | WM8400_SYSCLK_ENA); /* Enable POBCTRL, SOFT_ST, VMIDTOG and BUFDCOPEN */ - wm8400_write(codec, WM8400_ANTIPOP2, WM8400_SOFTST | + snd_soc_write(codec, WM8400_ANTIPOP2, WM8400_SOFTST | WM8400_BUFDCOPEN | WM8400_POBCTRL); msleep(50); /* Enable VREF & VMID at 2x50k */ - val = wm8400_read(codec, WM8400_POWER_MANAGEMENT_1); + val = snd_soc_read(codec, WM8400_POWER_MANAGEMENT_1); val |= 0x2 | WM8400_VREF_ENA; - wm8400_write(codec, WM8400_POWER_MANAGEMENT_1, val); + snd_soc_write(codec, WM8400_POWER_MANAGEMENT_1, val); /* Enable BUFIOEN */ - wm8400_write(codec, WM8400_ANTIPOP2, WM8400_SOFTST | + snd_soc_write(codec, WM8400_ANTIPOP2, WM8400_SOFTST | WM8400_BUFDCOPEN | WM8400_POBCTRL | WM8400_BUFIOEN); /* disable POBCTRL, SOFT_ST and BUFDCOPEN */ - wm8400_write(codec, WM8400_ANTIPOP2, WM8400_BUFIOEN); + snd_soc_write(codec, WM8400_ANTIPOP2, WM8400_BUFIOEN); } /* VMID=2*300k */ - val = wm8400_read(codec, WM8400_POWER_MANAGEMENT_1) & + val = snd_soc_read(codec, WM8400_POWER_MANAGEMENT_1) & ~WM8400_VMID_MODE_MASK; - wm8400_write(codec, WM8400_POWER_MANAGEMENT_1, val | 0x4); + snd_soc_write(codec, WM8400_POWER_MANAGEMENT_1, val | 0x4); break; case SND_SOC_BIAS_OFF: /* Enable POBCTRL and SOFT_ST */ - wm8400_write(codec, WM8400_ANTIPOP2, WM8400_SOFTST | + snd_soc_write(codec, WM8400_ANTIPOP2, WM8400_SOFTST | WM8400_POBCTRL | WM8400_BUFIOEN); /* Enable POBCTRL, SOFT_ST and BUFDCOPEN */ - wm8400_write(codec, WM8400_ANTIPOP2, WM8400_SOFTST | + snd_soc_write(codec, WM8400_ANTIPOP2, WM8400_SOFTST | WM8400_BUFDCOPEN | WM8400_POBCTRL | WM8400_BUFIOEN); /* mute DAC */ - val = wm8400_read(codec, WM8400_DAC_CTRL); - wm8400_write(codec, WM8400_DAC_CTRL, val | WM8400_DAC_MUTE); + val = snd_soc_read(codec, WM8400_DAC_CTRL); + snd_soc_write(codec, WM8400_DAC_CTRL, val | WM8400_DAC_MUTE); /* Enable any disabled outputs */ - val = wm8400_read(codec, WM8400_POWER_MANAGEMENT_1); + val = snd_soc_read(codec, WM8400_POWER_MANAGEMENT_1); val |= WM8400_SPK_ENA | WM8400_OUT3_ENA | WM8400_OUT4_ENA | WM8400_LOUT_ENA | WM8400_ROUT_ENA; - wm8400_write(codec, WM8400_POWER_MANAGEMENT_1, val); + snd_soc_write(codec, WM8400_POWER_MANAGEMENT_1, val); /* Disable VMID */ val &= ~WM8400_VMID_MODE_MASK; - wm8400_write(codec, WM8400_POWER_MANAGEMENT_1, val); + snd_soc_write(codec, WM8400_POWER_MANAGEMENT_1, val); msleep(300); /* Enable all output discharge bits */ - wm8400_write(codec, WM8400_ANTIPOP1, WM8400_DIS_LLINE | + snd_soc_write(codec, WM8400_ANTIPOP1, WM8400_DIS_LLINE | WM8400_DIS_RLINE | WM8400_DIS_OUT3 | WM8400_DIS_OUT4 | WM8400_DIS_LOUT | WM8400_DIS_ROUT); /* Disable VREF */ val &= ~WM8400_VREF_ENA; - wm8400_write(codec, WM8400_POWER_MANAGEMENT_1, val); + snd_soc_write(codec, WM8400_POWER_MANAGEMENT_1, val); /* disable POBCTRL, SOFT_ST and BUFDCOPEN */ - wm8400_write(codec, WM8400_ANTIPOP2, 0x0); + snd_soc_write(codec, WM8400_ANTIPOP2, 0x0); ret = regulator_bulk_disable(ARRAY_SIZE(power), &power[0]); @@ -1385,19 +1384,19 @@ static int wm8400_codec_probe(struct snd_soc_codec *codec) wm8400_codec_reset(codec); - reg = wm8400_read(codec, WM8400_POWER_MANAGEMENT_1); - wm8400_write(codec, WM8400_POWER_MANAGEMENT_1, reg | WM8400_CODEC_ENA); + reg = snd_soc_read(codec, WM8400_POWER_MANAGEMENT_1); + snd_soc_write(codec, WM8400_POWER_MANAGEMENT_1, reg | WM8400_CODEC_ENA); /* Latch volume update bits */ - reg = wm8400_read(codec, WM8400_LEFT_LINE_INPUT_1_2_VOLUME); - wm8400_write(codec, WM8400_LEFT_LINE_INPUT_1_2_VOLUME, + reg = snd_soc_read(codec, WM8400_LEFT_LINE_INPUT_1_2_VOLUME); + snd_soc_write(codec, WM8400_LEFT_LINE_INPUT_1_2_VOLUME, reg & WM8400_IPVU); - reg = wm8400_read(codec, WM8400_RIGHT_LINE_INPUT_1_2_VOLUME); - wm8400_write(codec, WM8400_RIGHT_LINE_INPUT_1_2_VOLUME, + reg = snd_soc_read(codec, WM8400_RIGHT_LINE_INPUT_1_2_VOLUME); + snd_soc_write(codec, WM8400_RIGHT_LINE_INPUT_1_2_VOLUME, reg & WM8400_IPVU); - wm8400_write(codec, WM8400_LEFT_OUTPUT_VOLUME, 0x50 | (1<<8)); - wm8400_write(codec, WM8400_RIGHT_OUTPUT_VOLUME, 0x50 | (1<<8)); + 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)) { ret = -EINVAL; @@ -1414,8 +1413,8 @@ static int wm8400_codec_remove(struct snd_soc_codec *codec) { u16 reg; - reg = wm8400_read(codec, WM8400_POWER_MANAGEMENT_1); - wm8400_write(codec, WM8400_POWER_MANAGEMENT_1, + reg = snd_soc_read(codec, WM8400_POWER_MANAGEMENT_1); + snd_soc_write(codec, WM8400_POWER_MANAGEMENT_1, reg & (~WM8400_CODEC_ENA)); regulator_bulk_free(ARRAY_SIZE(power), power); @@ -1428,7 +1427,7 @@ static struct snd_soc_codec_driver soc_codec_dev_wm8400 = { .remove = wm8400_codec_remove, .suspend = wm8400_suspend, .resume = wm8400_resume, - .read = wm8400_read, + .read = snd_soc_read, .write = wm8400_write, .set_bias_level = wm8400_set_bias_level, diff --git a/sound/soc/codecs/wm8510.c b/sound/soc/codecs/wm8510.c index 9166126bd31..56a049555e2 100644 --- a/sound/soc/codecs/wm8510.c +++ b/sound/soc/codecs/wm8510.c @@ -392,8 +392,7 @@ static int wm8510_pcm_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_codec *codec = dai->codec; u16 iface = snd_soc_read(codec, WM8510_IFACE) & 0x19f; u16 adn = snd_soc_read(codec, WM8510_ADD) & 0x1f1; diff --git a/sound/soc/codecs/wm8523.c b/sound/soc/codecs/wm8523.c index 7fea2c3bf7e..1c3ffb290cd 100644 --- a/sound/soc/codecs/wm8523.c +++ b/sound/soc/codecs/wm8523.c @@ -145,8 +145,7 @@ static int wm8523_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_codec *codec = dai->codec; struct wm8523_priv *wm8523 = snd_soc_codec_get_drvdata(codec); int i; u16 aifctrl1 = snd_soc_read(codec, WM8523_AIF_CTRL1); diff --git a/sound/soc/codecs/wm8728.c b/sound/soc/codecs/wm8728.c index fc3d59e4908..1467f97dce2 100644 --- a/sound/soc/codecs/wm8728.c +++ b/sound/soc/codecs/wm8728.c @@ -88,8 +88,7 @@ static int wm8728_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_codec *codec = dai->codec; u16 dac = snd_soc_read(codec, WM8728_DACCTL); dac &= ~0x18; diff --git a/sound/soc/codecs/wm8731.c b/sound/soc/codecs/wm8731.c index a32caa72bd7..9d1b9b0271f 100644 --- a/sound/soc/codecs/wm8731.c +++ b/sound/soc/codecs/wm8731.c @@ -635,16 +635,17 @@ static int __devinit wm8731_spi_probe(struct spi_device *spi) struct wm8731_priv *wm8731; int ret; - wm8731 = kzalloc(sizeof(struct wm8731_priv), GFP_KERNEL); + wm8731 = devm_kzalloc(&spi->dev, sizeof(struct wm8731_priv), + GFP_KERNEL); if (wm8731 == NULL) return -ENOMEM; - wm8731->regmap = regmap_init_spi(spi, &wm8731_regmap); + wm8731->regmap = devm_regmap_init_spi(spi, &wm8731_regmap); if (IS_ERR(wm8731->regmap)) { ret = PTR_ERR(wm8731->regmap); dev_err(&spi->dev, "Failed to allocate register map: %d\n", ret); - goto err; + return ret; } spi_set_drvdata(spi, wm8731); @@ -653,25 +654,15 @@ static int __devinit wm8731_spi_probe(struct spi_device *spi) &soc_codec_dev_wm8731, &wm8731_dai, 1); if (ret != 0) { dev_err(&spi->dev, "Failed to register CODEC: %d\n", ret); - goto err_regmap; + return ret; } return 0; - -err_regmap: - regmap_exit(wm8731->regmap); -err: - kfree(wm8731); - return ret; } static int __devexit wm8731_spi_remove(struct spi_device *spi) { - struct wm8731_priv *wm8731 = spi_get_drvdata(spi); - snd_soc_unregister_codec(&spi->dev); - regmap_exit(wm8731->regmap); - kfree(wm8731); return 0; } @@ -693,16 +684,17 @@ static __devinit int wm8731_i2c_probe(struct i2c_client *i2c, struct wm8731_priv *wm8731; int ret; - wm8731 = kzalloc(sizeof(struct wm8731_priv), GFP_KERNEL); + wm8731 = devm_kzalloc(&i2c->dev, sizeof(struct wm8731_priv), + GFP_KERNEL); if (wm8731 == NULL) return -ENOMEM; - wm8731->regmap = regmap_init_i2c(i2c, &wm8731_regmap); + wm8731->regmap = devm_regmap_init_i2c(i2c, &wm8731_regmap); if (IS_ERR(wm8731->regmap)) { ret = PTR_ERR(wm8731->regmap); dev_err(&i2c->dev, "Failed to allocate register map: %d\n", ret); - goto err; + return ret; } i2c_set_clientdata(i2c, wm8731); @@ -711,24 +703,15 @@ static __devinit int wm8731_i2c_probe(struct i2c_client *i2c, &soc_codec_dev_wm8731, &wm8731_dai, 1); if (ret != 0) { dev_err(&i2c->dev, "Failed to register CODEC: %d\n", ret); - goto err_regmap; + return ret; } return 0; - -err_regmap: - regmap_exit(wm8731->regmap); -err: - kfree(wm8731); - return ret; } static __devexit int wm8731_i2c_remove(struct i2c_client *client) { - struct wm8731_priv *wm8731 = i2c_get_clientdata(client); snd_soc_unregister_codec(&client->dev); - regmap_exit(wm8731->regmap); - kfree(wm8731); return 0; } diff --git a/sound/soc/codecs/wm8737.c b/sound/soc/codecs/wm8737.c index 4fe9d191e27..d0520124616 100644 --- a/sound/soc/codecs/wm8737.c +++ b/sound/soc/codecs/wm8737.c @@ -329,8 +329,7 @@ static int wm8737_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_codec *codec = dai->codec; struct wm8737_priv *wm8737 = snd_soc_codec_get_drvdata(codec); int i; u16 clocking = 0; diff --git a/sound/soc/codecs/wm8741.c b/sound/soc/codecs/wm8741.c index 3941f50bf18..6e849cb0424 100644 --- a/sound/soc/codecs/wm8741.c +++ b/sound/soc/codecs/wm8741.c @@ -203,8 +203,7 @@ static int wm8741_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_codec *codec = dai->codec; struct wm8741_priv *wm8741 = snd_soc_codec_get_drvdata(codec); u16 iface = snd_soc_read(codec, WM8741_FORMAT_CONTROL) & 0x1FC; int i; diff --git a/sound/soc/codecs/wm8750.c b/sound/soc/codecs/wm8750.c index e4c50ce7d9c..89151ca5e77 100644 --- a/sound/soc/codecs/wm8750.c +++ b/sound/soc/codecs/wm8750.c @@ -547,8 +547,7 @@ static int wm8750_pcm_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_codec *codec = dai->codec; struct wm8750_priv *wm8750 = snd_soc_codec_get_drvdata(codec); u16 iface = snd_soc_read(codec, WM8750_IFACE) & 0x1f3; u16 srate = snd_soc_read(codec, WM8750_SRATE) & 0x1c0; diff --git a/sound/soc/codecs/wm8753.c b/sound/soc/codecs/wm8753.c index e27e7b62b36..a26482cd765 100644 --- a/sound/soc/codecs/wm8753.c +++ b/sound/soc/codecs/wm8753.c @@ -931,8 +931,7 @@ static int wm8753_pcm_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_codec *codec = dai->codec; struct wm8753_priv *wm8753 = snd_soc_codec_get_drvdata(codec); u16 voice = snd_soc_read(codec, WM8753_PCM) & 0x01f3; u16 srate = snd_soc_read(codec, WM8753_SRATE1) & 0x017f; @@ -1161,8 +1160,7 @@ static int wm8753_i2s_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_codec *codec = dai->codec; struct wm8753_priv *wm8753 = snd_soc_codec_get_drvdata(codec); u16 srate = snd_soc_read(codec, WM8753_SRATE1) & 0x01c0; u16 hifi = snd_soc_read(codec, WM8753_HIFI) & 0x01f3; diff --git a/sound/soc/codecs/wm8900.c b/sound/soc/codecs/wm8900.c index f18c554efc9..077c9628c70 100644 --- a/sound/soc/codecs/wm8900.c +++ b/sound/soc/codecs/wm8900.c @@ -610,8 +610,7 @@ static int wm8900_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_codec *codec = dai->codec; u16 reg; reg = snd_soc_read(codec, WM8900_REG_AUDIO1) & ~0x60; diff --git a/sound/soc/codecs/wm8903.c b/sound/soc/codecs/wm8903.c index c91fb2f99c1..86b8a292659 100644 --- a/sound/soc/codecs/wm8903.c +++ b/sound/soc/codecs/wm8903.c @@ -1432,8 +1432,7 @@ static int wm8903_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_codec *codec =rtd->codec; + struct snd_soc_codec *codec = dai->codec; struct wm8903_priv *wm8903 = snd_soc_codec_get_drvdata(codec); int fs = params_rate(params); int bclk; diff --git a/sound/soc/codecs/wm8940.c b/sound/soc/codecs/wm8940.c index d2883affea3..481a3d9cfe4 100644 --- a/sound/soc/codecs/wm8940.c +++ b/sound/soc/codecs/wm8940.c @@ -371,8 +371,7 @@ static int wm8940_i2s_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_codec *codec = dai->codec; u16 iface = snd_soc_read(codec, WM8940_IFACE) & 0xFD9F; u16 addcntrl = snd_soc_read(codec, WM8940_ADDCNTRL) & 0xFFF1; u16 companding = snd_soc_read(codec, diff --git a/sound/soc/codecs/wm8960.c b/sound/soc/codecs/wm8960.c index 840d72086d0..8bc659d8dd2 100644 --- a/sound/soc/codecs/wm8960.c +++ b/sound/soc/codecs/wm8960.c @@ -505,8 +505,7 @@ static int wm8960_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_codec *codec = dai->codec; struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); u16 iface = snd_soc_read(codec, WM8960_IFACE1) & 0xfff3; int i; diff --git a/sound/soc/codecs/wm8962.c b/sound/soc/codecs/wm8962.c index 15d467ff91b..0cfce9999c8 100644 --- a/sound/soc/codecs/wm8962.c +++ b/sound/soc/codecs/wm8962.c @@ -1478,7 +1478,8 @@ static const DECLARE_TLV_DB_SCALE(eq_tlv, -1200, 100, 0); static int wm8962_dsp2_write_config(struct snd_soc_codec *codec) { - return 0; + return regcache_sync_region(codec->control_data, + WM8962_HDBASS_AI_1, WM8962_MAX_REGISTER); } static int wm8962_dsp2_set_enable(struct snd_soc_codec *codec, u16 val) @@ -1755,10 +1756,22 @@ SOC_DOUBLE_R_TLV("EQ4 Volume", WM8962_EQ3, WM8962_EQ23, SOC_DOUBLE_R_TLV("EQ5 Volume", WM8962_EQ3, WM8962_EQ23, WM8962_EQL_B5_GAIN_SHIFT, 31, 0, eq_tlv), +SOC_SINGLE("3D Switch", WM8962_THREED1, 0, 1, 0), +SND_SOC_BYTES_MASK("3D Coefficients", WM8962_THREED1, 4, WM8962_THREED_ENA), + +SOC_SINGLE("DF1 Switch", WM8962_DF1, 0, 1, 0), +SND_SOC_BYTES_MASK("DF1 Coefficients", WM8962_DF1, 7, WM8962_DF1_ENA), + +SOC_SINGLE("DRC Switch", WM8962_DRC_1, 0, 1, 0), +SND_SOC_BYTES_MASK("DRC Coefficients", WM8962_DRC_1, 5, WM8962_DRC_ENA), + WM8962_DSP2_ENABLE("VSS Switch", WM8962_VSS_ENA_SHIFT), +SND_SOC_BYTES("VSS Coefficients", WM8962_VSS_XHD2_1, 148), WM8962_DSP2_ENABLE("HPF1 Switch", WM8962_HPF1_ENA_SHIFT), WM8962_DSP2_ENABLE("HPF2 Switch", WM8962_HPF2_ENA_SHIFT), +SND_SOC_BYTES("HPF Coefficients", WM8962_LHPF2, 1), WM8962_DSP2_ENABLE("HD Bass Switch", WM8962_HDBASS_ENA_SHIFT), +SND_SOC_BYTES("HD Bass Coefficients", WM8962_HDBASS_AI_1, 30), }; static const struct snd_kcontrol_new wm8962_spk_mono_controls[] = { @@ -2519,8 +2532,7 @@ static int wm8962_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_codec *codec = dai->codec; struct wm8962_priv *wm8962 = snd_soc_codec_get_drvdata(codec); int i; int aif0 = 0; diff --git a/sound/soc/codecs/wm8971.c b/sound/soc/codecs/wm8971.c index 28fe59e3ce0..eef783f6b6d 100644 --- a/sound/soc/codecs/wm8971.c +++ b/sound/soc/codecs/wm8971.c @@ -478,8 +478,7 @@ static int wm8971_pcm_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_codec *codec = dai->codec; struct wm8971_priv *wm8971 = snd_soc_codec_get_drvdata(codec); u16 iface = snd_soc_read(codec, WM8971_IFACE) & 0x1f3; u16 srate = snd_soc_read(codec, WM8971_SRATE) & 0x1c0; diff --git a/sound/soc/codecs/wm8978.c b/sound/soc/codecs/wm8978.c index 72d5fdcd3cc..a5be3adecf7 100644 --- a/sound/soc/codecs/wm8978.c +++ b/sound/soc/codecs/wm8978.c @@ -723,8 +723,7 @@ static int wm8978_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_codec *codec = dai->codec; struct wm8978_priv *wm8978 = snd_soc_codec_get_drvdata(codec); /* Word length mask = 0x60 */ u16 iface_ctl = snd_soc_read(codec, WM8978_AUDIO_INTERFACE) & ~0x60; diff --git a/sound/soc/codecs/wm8988.c b/sound/soc/codecs/wm8988.c index 6cdf6a2bc28..1d4c5cf47b0 100644 --- a/sound/soc/codecs/wm8988.c +++ b/sound/soc/codecs/wm8988.c @@ -668,8 +668,7 @@ static int wm8988_pcm_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_codec *codec = dai->codec; struct wm8988_priv *wm8988 = snd_soc_codec_get_drvdata(codec); u16 iface = snd_soc_read(codec, WM8988_IFACE) & 0x1f3; u16 srate = snd_soc_read(codec, WM8988_SRATE) & 0x180; diff --git a/sound/soc/codecs/wm8990.c b/sound/soc/codecs/wm8990.c index 9d242351e6e..db63c97ddf5 100644 --- a/sound/soc/codecs/wm8990.c +++ b/sound/soc/codecs/wm8990.c @@ -1112,8 +1112,7 @@ static int wm8990_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_codec *codec = dai->codec; u16 audio1 = snd_soc_read(codec, WM8990_AUDIO_INTERFACE_1); audio1 &= ~WM8990_AIF_WL_MASK; diff --git a/sound/soc/codecs/wm8993.c b/sound/soc/codecs/wm8993.c index d256a934064..36acfccab99 100644 --- a/sound/soc/codecs/wm8993.c +++ b/sound/soc/codecs/wm8993.c @@ -218,7 +218,6 @@ struct wm8993_priv { unsigned int sysclk_rate; unsigned int fs; unsigned int bclk; - int class_w_users; unsigned int fll_fref; unsigned int fll_fout; int fll_src; @@ -824,84 +823,6 @@ static int clk_sys_event(struct snd_soc_dapm_widget *w, return 0; } -/* - * When used with DAC outputs only the WM8993 charge pump supports - * operation in class W mode, providing very low power consumption - * when used with digital sources. Enable and disable this mode - * automatically depending on the mixer configuration. - * - * Currently the only supported paths are the direct DAC->headphone - * paths (which provide minimum power consumption anyway). - */ -static int class_w_put(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol); - struct snd_soc_dapm_widget *widget = wlist->widgets[0]; - struct snd_soc_codec *codec = widget->codec; - struct wm8993_priv *wm8993 = snd_soc_codec_get_drvdata(codec); - int ret; - - /* Turn it off if we're using the main output mixer */ - if (ucontrol->value.integer.value[0] == 0) { - if (wm8993->class_w_users == 0) { - dev_dbg(codec->dev, "Disabling Class W\n"); - snd_soc_update_bits(codec, WM8993_CLASS_W_0, - WM8993_CP_DYN_FREQ | - WM8993_CP_DYN_V, - 0); - } - wm8993->class_w_users++; - wm8993->hubs_data.class_w = true; - } - - /* Implement the change */ - ret = snd_soc_dapm_put_enum_double(kcontrol, ucontrol); - - /* Enable it if we're using the direct DAC path */ - if (ucontrol->value.integer.value[0] == 1) { - if (wm8993->class_w_users == 1) { - dev_dbg(codec->dev, "Enabling Class W\n"); - snd_soc_update_bits(codec, WM8993_CLASS_W_0, - WM8993_CP_DYN_FREQ | - WM8993_CP_DYN_V, - WM8993_CP_DYN_FREQ | - WM8993_CP_DYN_V); - } - wm8993->class_w_users--; - wm8993->hubs_data.class_w = false; - } - - dev_dbg(codec->dev, "Indirect DAC use count now %d\n", - wm8993->class_w_users); - - return ret; -} - -#define SOC_DAPM_ENUM_W(xname, xenum) \ -{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ - .info = snd_soc_info_enum_double, \ - .get = snd_soc_dapm_get_enum_double, \ - .put = class_w_put, \ - .private_value = (unsigned long)&xenum } - -static const char *hp_mux_text[] = { - "Mixer", - "DAC", -}; - -static const struct soc_enum hpl_enum = - SOC_ENUM_SINGLE(WM8993_OUTPUT_MIXER1, 8, 2, hp_mux_text); - -static const struct snd_kcontrol_new hpl_mux = - SOC_DAPM_ENUM_W("Left Headphone Mux", hpl_enum); - -static const struct soc_enum hpr_enum = - SOC_ENUM_SINGLE(WM8993_OUTPUT_MIXER2, 8, 2, hp_mux_text); - -static const struct snd_kcontrol_new hpr_mux = - SOC_DAPM_ENUM_W("Right Headphone Mux", hpr_enum); - static const struct snd_kcontrol_new left_speaker_mixer[] = { SOC_DAPM_SINGLE("Input Switch", WM8993_SPEAKER_MIXER, 7, 1, 0), SOC_DAPM_SINGLE("IN1LP Switch", WM8993_SPEAKER_MIXER, 5, 1, 0), @@ -988,8 +909,8 @@ SND_SOC_DAPM_MUX("DACR Sidetone", SND_SOC_NOPM, 0, 0, &sidetoner_mux), SND_SOC_DAPM_DAC("DACL", NULL, WM8993_POWER_MANAGEMENT_3, 1, 0), SND_SOC_DAPM_DAC("DACR", NULL, WM8993_POWER_MANAGEMENT_3, 0, 0), -SND_SOC_DAPM_MUX("Left Headphone Mux", SND_SOC_NOPM, 0, 0, &hpl_mux), -SND_SOC_DAPM_MUX("Right Headphone Mux", SND_SOC_NOPM, 0, 0, &hpr_mux), +SND_SOC_DAPM_MUX("Left Headphone Mux", SND_SOC_NOPM, 0, 0, &wm_hubs_hpl_mux), +SND_SOC_DAPM_MUX("Right Headphone Mux", SND_SOC_NOPM, 0, 0, &wm_hubs_hpr_mux), SND_SOC_DAPM_MIXER("SPKL", WM8993_POWER_MANAGEMENT_3, 8, 0, left_speaker_mixer, ARRAY_SIZE(left_speaker_mixer)), @@ -1579,9 +1500,6 @@ static int wm8993_probe(struct snd_soc_codec *codec) return ret; } - /* By default we're using the output mixers */ - wm8993->class_w_users = 2; - /* Latch volume update bits and default ZC on */ snd_soc_update_bits(codec, WM8993_RIGHT_DAC_DIGITAL_VOLUME, WM8993_DAC_VU, WM8993_DAC_VU); diff --git a/sound/soc/codecs/wm8994.c b/sound/soc/codecs/wm8994.c index 2de12ebe43b..993639d694c 100644 --- a/sound/soc/codecs/wm8994.c +++ b/sound/soc/codecs/wm8994.c @@ -70,8 +70,8 @@ static const struct wm8958_micd_rate micdet_rates[] = { static const struct wm8958_micd_rate jackdet_rates[] = { { 32768, true, 0, 1 }, { 32768, false, 0, 1 }, - { 44100 * 256, true, 7, 10 }, - { 44100 * 256, false, 7, 10 }, + { 44100 * 256, true, 10, 10 }, + { 44100 * 256, false, 7, 8 }, }; static void wm8958_micd_set_rate(struct snd_soc_codec *codec) @@ -82,7 +82,8 @@ static void wm8958_micd_set_rate(struct snd_soc_codec *codec) const struct wm8958_micd_rate *rates; int num_rates; - if (wm8994->jack_cb != wm8958_default_micdet) + if (!(wm8994->pdata && wm8994->pdata->micd_rates) && + wm8994->jack_cb != wm8958_default_micdet) return; idle = !wm8994->jack_mic; @@ -118,6 +119,10 @@ static void wm8958_micd_set_rate(struct snd_soc_codec *codec) val = rates[best].start << WM8958_MICD_BIAS_STARTTIME_SHIFT | rates[best].rate << WM8958_MICD_RATE_SHIFT; + dev_dbg(codec->dev, "MICD rate %d,%d for %dHz %s\n", + rates[best].start, rates[best].rate, sysclk, + idle ? "idle" : "active"); + snd_soc_update_bits(codec, WM8958_MIC_DETECT_1, WM8958_MICD_BIAS_STARTTIME_MASK | WM8958_MICD_RATE_MASK, val); @@ -398,7 +403,7 @@ static void wm8994_set_retune_mobile(struct snd_soc_codec *codec, int block) wm8994->dac_rates[iface]); /* The EQ will be disabled while reconfiguring it, remember the - * current configuration. + * current configuration. */ save = snd_soc_read(codec, base); save &= WM8994_AIF1DAC1_EQ_ENA; @@ -689,6 +694,9 @@ static void wm1811_jackdet_set_mode(struct snd_soc_codec *codec, u16 mode) if (!wm8994->jackdet || !wm8994->jack_cb) return; + if (!wm8994->jackdet || !wm8994->jack_cb) + return; + if (wm8994->active_refcount) mode = WM1811_JACKDET_MODE_AUDIO; @@ -784,7 +792,7 @@ static void vmid_reference(struct snd_soc_codec *codec) switch (wm8994->vmid_mode) { default: - WARN_ON(0 == "Invalid VMID mode"); + WARN_ON(NULL == "Invalid VMID mode"); case WM8994_VMID_NORMAL: /* Startup bias, VMID ramp & buffer */ snd_soc_update_bits(codec, WM8994_ANTIPOP_2, @@ -937,27 +945,12 @@ static int vmid_event(struct snd_soc_dapm_widget *w, return 0; } -static void wm8994_update_class_w(struct snd_soc_codec *codec) +static bool wm8994_check_class_w_digital(struct snd_soc_codec *codec) { - struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); - int enable = 1; int source = 0; /* GCC flow analysis can't track enable */ int reg, reg_r; - /* Only support direct DAC->headphone paths */ - reg = snd_soc_read(codec, WM8994_OUTPUT_MIXER_1); - if (!(reg & WM8994_DAC1L_TO_HPOUT1L)) { - dev_vdbg(codec->dev, "HPL connected to output mixer\n"); - enable = 0; - } - - reg = snd_soc_read(codec, WM8994_OUTPUT_MIXER_2); - if (!(reg & WM8994_DAC1R_TO_HPOUT1R)) { - dev_vdbg(codec->dev, "HPR connected to output mixer\n"); - enable = 0; - } - - /* We also need the same setting for L/R and only one path */ + /* We also need the same AIF source for L/R and only one path */ reg = snd_soc_read(codec, WM8994_DAC1_LEFT_MIXER_ROUTING); switch (reg) { case WM8994_AIF2DACL_TO_DAC1L: @@ -974,30 +967,20 @@ static void wm8994_update_class_w(struct snd_soc_codec *codec) break; default: dev_vdbg(codec->dev, "DAC mixer setting: %x\n", reg); - enable = 0; - break; + return false; } reg_r = snd_soc_read(codec, WM8994_DAC1_RIGHT_MIXER_ROUTING); if (reg_r != reg) { dev_vdbg(codec->dev, "Left and right DAC mixers different\n"); - enable = 0; + return false; } - if (enable) { - dev_dbg(codec->dev, "Class W enabled\n"); - snd_soc_update_bits(codec, WM8994_CLASS_W_1, - WM8994_CP_DYN_PWR | - WM8994_CP_DYN_SRC_SEL_MASK, - source | WM8994_CP_DYN_PWR); - wm8994->hubs.class_w = true; - - } else { - dev_dbg(codec->dev, "Class W disabled\n"); - snd_soc_update_bits(codec, WM8994_CLASS_W_1, - WM8994_CP_DYN_PWR, 0); - wm8994->hubs.class_w = false; - } + /* Set the source up */ + snd_soc_update_bits(codec, WM8994_CLASS_W_1, + WM8994_CP_DYN_SRC_SEL_MASK, source); + + return true; } static int aif1clk_ev(struct snd_soc_dapm_widget *w, @@ -1280,45 +1263,6 @@ static int dac_ev(struct snd_soc_dapm_widget *w, return 0; } -static const char *hp_mux_text[] = { - "Mixer", - "DAC", -}; - -#define WM8994_HP_ENUM(xname, xenum) \ -{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ - .info = snd_soc_info_enum_double, \ - .get = snd_soc_dapm_get_enum_double, \ - .put = wm8994_put_hp_enum, \ - .private_value = (unsigned long)&xenum } - -static int wm8994_put_hp_enum(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol); - struct snd_soc_dapm_widget *w = wlist->widgets[0]; - struct snd_soc_codec *codec = w->codec; - int ret; - - ret = snd_soc_dapm_put_enum_double(kcontrol, ucontrol); - - wm8994_update_class_w(codec); - - return ret; -} - -static const struct soc_enum hpl_enum = - SOC_ENUM_SINGLE(WM8994_OUTPUT_MIXER_1, 8, 2, hp_mux_text); - -static const struct snd_kcontrol_new hpl_mux = - WM8994_HP_ENUM("Left Headphone Mux", hpl_enum); - -static const struct soc_enum hpr_enum = - SOC_ENUM_SINGLE(WM8994_OUTPUT_MIXER_2, 8, 2, hp_mux_text); - -static const struct snd_kcontrol_new hpr_mux = - WM8994_HP_ENUM("Right Headphone Mux", hpr_enum); - static const char *adc_mux_text[] = { "ADC", "DMIC", @@ -1430,7 +1374,7 @@ static int wm8994_put_class_w(struct snd_kcontrol *kcontrol, ret = snd_soc_dapm_put_volsw(kcontrol, ucontrol); - wm8994_update_class_w(codec); + wm_hubs_update_class_w(codec); return ret; } @@ -1524,7 +1468,7 @@ static const struct snd_kcontrol_new wm8958_aif3adc_mux = SOC_DAPM_ENUM("AIF3ADC Mux", wm8958_aif3adc_enum); static const char *mono_pcm_out_text[] = { - "None", "AIF2ADCL", "AIF2ADCR", + "None", "AIF2ADCL", "AIF2ADCR", }; static const struct soc_enum mono_pcm_out_enum = @@ -1573,9 +1517,9 @@ SND_SOC_DAPM_MIXER_E("SPKL", WM8994_POWER_MANAGEMENT_3, 8, 0, SND_SOC_DAPM_MIXER_E("SPKR", WM8994_POWER_MANAGEMENT_3, 9, 0, right_speaker_mixer, ARRAY_SIZE(right_speaker_mixer), late_enable_ev, SND_SOC_DAPM_PRE_PMU), -SND_SOC_DAPM_MUX_E("Left Headphone Mux", SND_SOC_NOPM, 0, 0, &hpl_mux, +SND_SOC_DAPM_MUX_E("Left Headphone Mux", SND_SOC_NOPM, 0, 0, &wm_hubs_hpl_mux, late_enable_ev, SND_SOC_DAPM_PRE_PMU), -SND_SOC_DAPM_MUX_E("Right Headphone Mux", SND_SOC_NOPM, 0, 0, &hpr_mux, +SND_SOC_DAPM_MUX_E("Right Headphone Mux", SND_SOC_NOPM, 0, 0, &wm_hubs_hpr_mux, late_enable_ev, SND_SOC_DAPM_PRE_PMU), SND_SOC_DAPM_POST("Late Disable PGA", late_disable_ev) @@ -1591,8 +1535,8 @@ SND_SOC_DAPM_MIXER("SPKL", WM8994_POWER_MANAGEMENT_3, 8, 0, left_speaker_mixer, ARRAY_SIZE(left_speaker_mixer)), SND_SOC_DAPM_MIXER("SPKR", WM8994_POWER_MANAGEMENT_3, 9, 0, right_speaker_mixer, ARRAY_SIZE(right_speaker_mixer)), -SND_SOC_DAPM_MUX("Left Headphone Mux", SND_SOC_NOPM, 0, 0, &hpl_mux), -SND_SOC_DAPM_MUX("Right Headphone Mux", SND_SOC_NOPM, 0, 0, &hpr_mux), +SND_SOC_DAPM_MUX("Left Headphone Mux", SND_SOC_NOPM, 0, 0, &wm_hubs_hpl_mux), +SND_SOC_DAPM_MUX("Right Headphone Mux", SND_SOC_NOPM, 0, 0, &wm_hubs_hpr_mux), }; static const struct snd_soc_dapm_widget wm8994_dac_revd_widgets[] = { @@ -1732,6 +1676,7 @@ SND_SOC_DAPM_MUX("AIF3ADC Mux", SND_SOC_NOPM, 0, 0, &wm8994_aif3adc_mux), }; static const struct snd_soc_dapm_widget wm8958_dapm_widgets[] = { +SND_SOC_DAPM_SUPPLY("AIF3", WM8994_POWER_MANAGEMENT_6, 5, 1, NULL, 0), SND_SOC_DAPM_MUX("Mono PCM Out Mux", SND_SOC_NOPM, 0, 0, &mono_pcm_out_mux), SND_SOC_DAPM_MUX("AIF2DACL Mux", SND_SOC_NOPM, 0, 0, &aif2dacl_src_mux), SND_SOC_DAPM_MUX("AIF2DACR Mux", SND_SOC_NOPM, 0, 0, &aif2dacr_src_mux), @@ -1972,6 +1917,9 @@ static const struct snd_soc_dapm_route wm8958_intercon[] = { { "AIF2DACR Mux", "AIF2", "AIF2DAC Mux" }, { "AIF2DACR Mux", "AIF3", "AIF3DACDAT" }, + { "AIF3DACDAT", NULL, "AIF3" }, + { "AIF3ADCDAT", NULL, "AIF3" }, + { "Mono PCM Out Mux", "AIF2ADCL", "AIF2ADCL" }, { "Mono PCM Out Mux", "AIF2ADCR", "AIF2ADCR" }, @@ -2068,24 +2016,20 @@ static int _wm8994_set_fll(struct snd_soc_codec *codec, int id, int src, struct wm8994 *control = wm8994->wm8994; int reg_offset, ret; struct fll_div fll; - u16 reg, aif1, aif2; + u16 reg, clk1, aif_reg, aif_src; unsigned long timeout; bool was_enabled; - aif1 = snd_soc_read(codec, WM8994_AIF1_CLOCKING_1) - & WM8994_AIF1CLK_ENA; - - aif2 = snd_soc_read(codec, WM8994_AIF2_CLOCKING_1) - & WM8994_AIF2CLK_ENA; - switch (id) { case WM8994_FLL1: reg_offset = 0; id = 0; + aif_src = 0x10; break; case WM8994_FLL2: reg_offset = 0x20; id = 1; + aif_src = 0x18; break; default: return -EINVAL; @@ -2127,16 +2071,33 @@ static int _wm8994_set_fll(struct snd_soc_codec *codec, int id, int src, if (ret < 0) return ret; - /* Gate the AIF clocks while we reclock */ - snd_soc_update_bits(codec, WM8994_AIF1_CLOCKING_1, - WM8994_AIF1CLK_ENA, 0); - snd_soc_update_bits(codec, WM8994_AIF2_CLOCKING_1, - WM8994_AIF2CLK_ENA, 0); + /* Make sure that we're not providing SYSCLK right now */ + clk1 = snd_soc_read(codec, WM8994_CLOCKING_1); + if (clk1 & WM8994_SYSCLK_SRC) + aif_reg = WM8994_AIF2_CLOCKING_1; + else + aif_reg = WM8994_AIF1_CLOCKING_1; + reg = snd_soc_read(codec, aif_reg); + + if ((reg & WM8994_AIF1CLK_ENA) && + (reg & WM8994_AIF1CLK_SRC_MASK) == aif_src) { + dev_err(codec->dev, "FLL%d is currently providing SYSCLK\n", + id + 1); + return -EBUSY; + } /* We always need to disable the FLL while reconfiguring */ snd_soc_update_bits(codec, WM8994_FLL1_CONTROL_1 + reg_offset, WM8994_FLL1_ENA, 0); + if (wm8994->fll_byp && src == WM8994_FLL_SRC_BCLK && + freq_in == freq_out && freq_out) { + dev_dbg(codec->dev, "Bypassing FLL%d\n", id + 1); + snd_soc_update_bits(codec, WM8994_FLL1_CONTROL_5 + reg_offset, + WM8958_FLL1_BYP, WM8958_FLL1_BYP); + goto out; + } + reg = (fll.outdiv << WM8994_FLL1_OUTDIV_SHIFT) | (fll.fll_fratio << WM8994_FLL1_FRATIO_SHIFT); snd_soc_update_bits(codec, WM8994_FLL1_CONTROL_2 + reg_offset, @@ -2151,6 +2112,7 @@ static int _wm8994_set_fll(struct snd_soc_codec *codec, int id, int src, fll.n << WM8994_FLL1_N_SHIFT); snd_soc_update_bits(codec, WM8994_FLL1_CONTROL_5 + reg_offset, + WM8958_FLL1_BYP | WM8994_FLL1_REFCLK_DIV_MASK | WM8994_FLL1_REFCLK_SRC_MASK, (fll.clk_ref_div << WM8994_FLL1_REFCLK_DIV_SHIFT) | @@ -2213,16 +2175,11 @@ static int _wm8994_set_fll(struct snd_soc_codec *codec, int id, int src, } } +out: wm8994->fll[id].in = freq_in; wm8994->fll[id].out = freq_out; wm8994->fll[id].src = src; - /* Enable any gated AIF clocks */ - snd_soc_update_bits(codec, WM8994_AIF1_CLOCKING_1, - WM8994_AIF1CLK_ENA, aif1); - snd_soc_update_bits(codec, WM8994_AIF2_CLOCKING_1, - WM8994_AIF2CLK_ENA, aif2); - configure_clock(codec); return 0; @@ -2290,7 +2247,7 @@ static int wm8994_set_dai_sysclk(struct snd_soc_dai *dai, case WM8994_SYSCLK_OPCLK: /* Special case - a division (times 10) is given and - * no effect on main clocking. + * no effect on main clocking. */ if (freq) { for (i = 0; i < ARRAY_SIZE(opclk_divs); i++) @@ -2792,33 +2749,6 @@ static int wm8994_aif3_hw_params(struct snd_pcm_substream *substream, return snd_soc_update_bits(codec, aif1_reg, WM8994_AIF1_WL_MASK, aif1); } -static void wm8994_aif_shutdown(struct snd_pcm_substream *substream, - struct snd_soc_dai *dai) -{ - struct snd_soc_codec *codec = dai->codec; - int rate_reg = 0; - - switch (dai->id) { - case 1: - rate_reg = WM8994_AIF1_RATE; - break; - case 2: - rate_reg = WM8994_AIF2_RATE; - break; - default: - break; - } - - /* If the DAI is idle then configure the divider tree for the - * lowest output rate to save a little power if the clock is - * still active (eg, because it is system clock). - */ - if (rate_reg && !dai->playback_active && !dai->capture_active) - snd_soc_update_bits(codec, rate_reg, - WM8994_AIF1_SR_MASK | - WM8994_AIF1CLK_RATE_MASK, 0x9); -} - static int wm8994_aif_mute(struct snd_soc_dai *codec_dai, int mute) { struct snd_soc_codec *codec = codec_dai->codec; @@ -2860,10 +2790,6 @@ static int wm8994_set_tristate(struct snd_soc_dai *codec_dai, int tristate) reg = WM8994_AIF2_MASTER_SLAVE; mask = WM8994_AIF2_TRI; break; - case 3: - reg = WM8994_POWER_MANAGEMENT_6; - mask = WM8994_AIF3_TRI; - break; default: return -EINVAL; } @@ -2900,7 +2826,6 @@ static const struct snd_soc_dai_ops wm8994_aif1_dai_ops = { .set_sysclk = wm8994_set_dai_sysclk, .set_fmt = wm8994_set_dai_fmt, .hw_params = wm8994_hw_params, - .shutdown = wm8994_aif_shutdown, .digital_mute = wm8994_aif_mute, .set_pll = wm8994_set_fll, .set_tristate = wm8994_set_tristate, @@ -2910,7 +2835,6 @@ static const struct snd_soc_dai_ops wm8994_aif2_dai_ops = { .set_sysclk = wm8994_set_dai_sysclk, .set_fmt = wm8994_set_dai_fmt, .hw_params = wm8994_hw_params, - .shutdown = wm8994_aif_shutdown, .digital_mute = wm8994_aif_mute, .set_pll = wm8994_set_fll, .set_tristate = wm8994_set_tristate, @@ -2918,7 +2842,6 @@ static const struct snd_soc_dai_ops wm8994_aif2_dai_ops = { static const struct snd_soc_dai_ops wm8994_aif3_dai_ops = { .hw_params = wm8994_aif3_hw_params, - .set_tristate = wm8994_set_tristate, }; static struct snd_soc_dai_driver wm8994_dai[] = { @@ -3126,14 +3049,14 @@ static void wm8994_handle_retune_mobile_pdata(struct wm8994_priv *wm8994) /* Expand the array... */ t = krealloc(wm8994->retune_mobile_texts, - sizeof(char *) * + sizeof(char *) * (wm8994->num_retune_mobile_texts + 1), GFP_KERNEL); if (t == NULL) continue; /* ...store the new entry... */ - t[wm8994->num_retune_mobile_texts] = + t[wm8994->num_retune_mobile_texts] = pdata->retune_mobile_cfgs[i].name; /* ...and remember the new version. */ @@ -3304,25 +3227,25 @@ int wm8994_mic_detect(struct snd_soc_codec *codec, struct snd_soc_jack *jack, } EXPORT_SYMBOL_GPL(wm8994_mic_detect); -static irqreturn_t wm8994_mic_irq(int irq, void *data) +static void wm8994_mic_work(struct work_struct *work) { - struct wm8994_priv *priv = data; - struct snd_soc_codec *codec = priv->codec; - int reg; + struct wm8994_priv *priv = container_of(work, + struct wm8994_priv, + mic_work.work); + struct regmap *regmap = priv->wm8994->regmap; + struct device *dev = priv->wm8994->dev; + unsigned int reg; + int ret; int report; -#ifndef CONFIG_SND_SOC_WM8994_MODULE - trace_snd_soc_jack_irq(dev_name(codec->dev)); -#endif - - reg = snd_soc_read(codec, WM8994_INTERRUPT_RAW_STATUS_2); - if (reg < 0) { - dev_err(codec->dev, "Failed to read microphone status: %d\n", - reg); - return IRQ_HANDLED; + ret = regmap_read(regmap, WM8994_INTERRUPT_RAW_STATUS_2, ®); + if (ret < 0) { + dev_err(dev, "Failed to read microphone status: %d\n", + ret); + return; } - dev_dbg(codec->dev, "Microphone status: %x\n", reg); + dev_dbg(dev, "Microphone status: %x\n", reg); report = 0; if (reg & WM8994_MIC1_DET_STS) { @@ -3361,6 +3284,20 @@ static irqreturn_t wm8994_mic_irq(int irq, void *data) snd_soc_jack_report(priv->micdet[1].jack, report, SND_JACK_HEADSET | SND_JACK_BTN_0); +} + +static irqreturn_t wm8994_mic_irq(int irq, void *data) +{ + struct wm8994_priv *priv = data; + struct snd_soc_codec *codec = priv->codec; + +#ifndef CONFIG_SND_SOC_WM8994_MODULE + trace_snd_soc_jack_irq(dev_name(codec->dev)); +#endif + + pm_wakeup_event(codec->dev, 300); + + schedule_delayed_work(&priv->mic_work, msecs_to_jiffies(250)); return IRQ_HANDLED; } @@ -3415,9 +3352,6 @@ static void wm8958_default_micdet(u16 status, void *data) wm8958_micd_set_rate(codec); - snd_soc_jack_report(wm8994->micdet[0].jack, SND_JACK_HEADPHONE, - SND_JACK_HEADSET); - /* If we have jackdet that will detect removal */ if (wm8994->jackdet) { mutex_lock(&wm8994->accdet_lock); @@ -3430,14 +3364,13 @@ static void wm8958_default_micdet(u16 status, void *data) mutex_unlock(&wm8994->accdet_lock); - if (wm8994->pdata->jd_ext_cap) { - mutex_lock(&codec->mutex); + if (wm8994->pdata->jd_ext_cap) snd_soc_dapm_disable_pin(&codec->dapm, "MICBIAS2"); - snd_soc_dapm_sync(&codec->dapm); - mutex_unlock(&codec->mutex); - } } + + snd_soc_jack_report(wm8994->micdet[0].jack, SND_JACK_HEADPHONE, + SND_JACK_HEADSET); } /* Report short circuit as a button */ @@ -3489,6 +3422,8 @@ static irqreturn_t wm1811_jackdet_irq(int irq, void *data) if (present) { dev_dbg(codec->dev, "Jack detected\n"); + wm8958_micd_set_rate(codec); + snd_soc_update_bits(codec, WM8958_MICBIAS2, WM8958_MICB2_DISCH, 0); @@ -3526,16 +3461,11 @@ static irqreturn_t wm1811_jackdet_irq(int irq, void *data) /* If required for an external cap force MICBIAS on */ if (wm8994->pdata->jd_ext_cap) { - mutex_lock(&codec->mutex); - if (present) snd_soc_dapm_force_enable_pin(&codec->dapm, "MICBIAS2"); else snd_soc_dapm_disable_pin(&codec->dapm, "MICBIAS2"); - - snd_soc_dapm_sync(&codec->dapm); - mutex_unlock(&codec->mutex); } if (present) @@ -3740,6 +3670,7 @@ static int wm8994_codec_probe(struct snd_soc_codec *codec) wm8994->codec = codec; mutex_init(&wm8994->accdet_lock); + INIT_DELAYED_WORK(&wm8994->mic_work, wm8994_mic_work); for (i = 0; i < ARRAY_SIZE(wm8994->fll_locked); i++) init_completion(&wm8994->fll_locked[i]); @@ -3783,13 +3714,22 @@ static int wm8994_codec_probe(struct snd_soc_codec *codec) case WM8958: wm8994->hubs.dcs_readback_mode = 1; wm8994->hubs.hp_startup_mode = 1; + + switch (wm8994->revision) { + case 0: + break; + default: + wm8994->fll_byp = true; + break; + } break; case WM1811: wm8994->hubs.dcs_readback_mode = 2; wm8994->hubs.no_series_update = 1; wm8994->hubs.hp_startup_mode = 1; - wm8994->hubs.no_cache_class_w = true; + wm8994->hubs.no_cache_dac_hp_direct = true; + wm8994->fll_byp = true; switch (wm8994->revision) { case 0: @@ -4010,7 +3950,8 @@ static int wm8994_codec_probe(struct snd_soc_codec *codec) break; } - wm8994_update_class_w(codec); + wm8994->hubs.check_class_w_digital = wm8994_check_class_w_digital; + wm_hubs_update_class_w(codec); wm8994_handle_pdata(wm8994); @@ -4075,7 +4016,6 @@ static int wm8994_codec_probe(struct snd_soc_codec *codec) ARRAY_SIZE(wm8994_dac_widgets)); break; } - wm_hubs_add_analogue_routes(codec, 0, 0); snd_soc_dapm_add_routes(dapm, intercon, ARRAY_SIZE(intercon)); @@ -4140,7 +4080,7 @@ err_irq: return ret; } -static int wm8994_codec_remove(struct snd_soc_codec *codec) +static int wm8994_codec_remove(struct snd_soc_codec *codec) { struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); struct wm8994 *control = wm8994->wm8994; @@ -4181,14 +4121,10 @@ static int wm8994_codec_remove(struct snd_soc_codec *codec) free_irq(wm8994->micdet_irq, wm8994); break; } - if (wm8994->mbc) - release_firmware(wm8994->mbc); - if (wm8994->mbc_vss) - release_firmware(wm8994->mbc_vss); - if (wm8994->enh_eq) - release_firmware(wm8994->enh_eq); + release_firmware(wm8994->mbc); + release_firmware(wm8994->mbc_vss); + release_firmware(wm8994->enh_eq); kfree(wm8994->retune_mobile_texts); - return 0; } diff --git a/sound/soc/codecs/wm8994.h b/sound/soc/codecs/wm8994.h index c724112998d..d77e06f0a67 100644 --- a/sound/soc/codecs/wm8994.h +++ b/sound/soc/codecs/wm8994.h @@ -12,6 +12,7 @@ #include <sound/soc.h> #include <linux/firmware.h> #include <linux/completion.h> +#include <linux/workqueue.h> #include "wm_hubs.h" @@ -79,6 +80,7 @@ struct wm8994_priv { struct wm8994_fll_config fll[2], fll_suspend[2]; struct completion fll_locked[2]; bool fll_locked_irq; + bool fll_byp; int vmid_refcount; int active_refcount; @@ -126,6 +128,7 @@ struct wm8994_priv { struct mutex accdet_lock; struct wm8994_micdet micdet[2]; + struct delayed_work mic_work; bool mic_detecting; bool jack_mic; int btn_mask; diff --git a/sound/soc/codecs/wm8996.c b/sound/soc/codecs/wm8996.c index 1fd63549404..8af422e38fd 100644 --- a/sound/soc/codecs/wm8996.c +++ b/sound/soc/codecs/wm8996.c @@ -1770,7 +1770,13 @@ static int wm8996_set_bias_level(struct snd_soc_codec *codec, switch (level) { case SND_SOC_BIAS_ON: + break; case SND_SOC_BIAS_PREPARE: + /* Put the MICBIASes into regulating mode */ + snd_soc_update_bits(codec, WM8996_MICBIAS_1, + WM8996_MICB1_MODE, 0); + snd_soc_update_bits(codec, WM8996_MICBIAS_2, + WM8996_MICB2_MODE, 0); break; case SND_SOC_BIAS_STANDBY: @@ -1793,6 +1799,12 @@ static int wm8996_set_bias_level(struct snd_soc_codec *codec, regcache_cache_only(codec->control_data, false); regcache_sync(codec->control_data); } + + /* Bypass the MICBIASes for lowest power */ + snd_soc_update_bits(codec, WM8996_MICBIAS_1, + WM8996_MICB1_MODE, WM8996_MICB1_MODE); + snd_soc_update_bits(codec, WM8996_MICBIAS_2, + WM8996_MICB2_MODE, WM8996_MICB2_MODE); break; case SND_SOC_BIAS_OFF: diff --git a/sound/soc/codecs/wm9081.c b/sound/soc/codecs/wm9081.c index 076c126ed9b..9328270df16 100644 --- a/sound/soc/codecs/wm9081.c +++ b/sound/soc/codecs/wm9081.c @@ -774,7 +774,7 @@ static const struct snd_soc_dapm_widget wm9081_dapm_widgets[] = { SND_SOC_DAPM_INPUT("IN1"), SND_SOC_DAPM_INPUT("IN2"), -SND_SOC_DAPM_DAC("DAC", "HiFi Playback", WM9081_POWER_MANAGEMENT, 0, 0), +SND_SOC_DAPM_DAC("DAC", NULL, WM9081_POWER_MANAGEMENT, 0, 0), SND_SOC_DAPM_MIXER_NAMED_CTL("Mixer", SND_SOC_NOPM, 0, 0, mixer, ARRAY_SIZE(mixer)), @@ -799,6 +799,7 @@ SND_SOC_DAPM_SUPPLY("TSENSE", WM9081_POWER_MANAGEMENT, 7, 0, NULL, 0), static const struct snd_soc_dapm_route wm9081_audio_paths[] = { { "DAC", NULL, "CLK_SYS" }, { "DAC", NULL, "CLK_DSP" }, + { "DAC", NULL, "AIF" }, { "Mixer", "IN1 Switch", "IN1" }, { "Mixer", "IN2 Switch", "IN2" }, @@ -1252,7 +1253,7 @@ static const struct snd_soc_dai_ops wm9081_dai_ops = { static struct snd_soc_dai_driver wm9081_dai = { .name = "wm9081-hifi", .playback = { - .stream_name = "HiFi Playback", + .stream_name = "AIF", .channels_min = 1, .channels_max = 2, .rates = WM9081_RATES, diff --git a/sound/soc/codecs/wm9705.c b/sound/soc/codecs/wm9705.c index cacc6a86b46..e8e782a0c78 100644 --- a/sound/soc/codecs/wm9705.c +++ b/sound/soc/codecs/wm9705.c @@ -236,9 +236,7 @@ static int ac97_write(struct snd_soc_codec *codec, unsigned int reg, static int ac97_prepare(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { - struct snd_pcm_runtime *runtime = substream->runtime; - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_codec *codec = dai->codec; int reg; u16 vra; @@ -250,7 +248,7 @@ static int ac97_prepare(struct snd_pcm_substream *substream, else reg = AC97_PCM_LR_ADC_RATE; - return ac97_write(codec, reg, runtime->rate); + return ac97_write(codec, reg, substream->runtime->rate); } #define WM9705_AC97_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | \ diff --git a/sound/soc/codecs/wm9712.c b/sound/soc/codecs/wm9712.c index b342ae50bcd..a1541414d90 100644 --- a/sound/soc/codecs/wm9712.c +++ b/sound/soc/codecs/wm9712.c @@ -467,11 +467,10 @@ static int ac97_write(struct snd_soc_codec *codec, unsigned int reg, static int ac97_prepare(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { - struct snd_pcm_runtime *runtime = substream->runtime; - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_codec *codec =rtd->codec; + struct snd_soc_codec *codec = dai->codec; int reg; u16 vra; + struct snd_pcm_runtime *runtime = substream->runtime; vra = ac97_read(codec, AC97_EXTENDED_STATUS); ac97_write(codec, AC97_EXTENDED_STATUS, vra | 0x1); @@ -487,10 +486,9 @@ static int ac97_prepare(struct snd_pcm_substream *substream, static int ac97_aux_prepare(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { - struct snd_pcm_runtime *runtime = substream->runtime; - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_codec *codec = dai->codec; u16 vra, xsle; + struct snd_pcm_runtime *runtime = substream->runtime; vra = ac97_read(codec, AC97_EXTENDED_STATUS); ac97_write(codec, AC97_EXTENDED_STATUS, vra | 0x1); diff --git a/sound/soc/codecs/wm_hubs.c b/sound/soc/codecs/wm_hubs.c index 6c028c47060..dfe957a47f2 100644 --- a/sound/soc/codecs/wm_hubs.c +++ b/sound/soc/codecs/wm_hubs.c @@ -109,12 +109,103 @@ irqreturn_t wm_hubs_dcs_done(int irq, void *data) } EXPORT_SYMBOL_GPL(wm_hubs_dcs_done); +static bool wm_hubs_dac_hp_direct(struct snd_soc_codec *codec) +{ + int reg; + + /* If we're going via the mixer we'll need to do additional checks */ + reg = snd_soc_read(codec, WM8993_OUTPUT_MIXER1); + if (!(reg & WM8993_DACL_TO_HPOUT1L)) { + if (reg & ~WM8993_DACL_TO_MIXOUTL) { + dev_vdbg(codec->dev, "Analogue paths connected: %x\n", + reg & ~WM8993_DACL_TO_HPOUT1L); + return false; + } else { + dev_vdbg(codec->dev, "HPL connected to mixer\n"); + } + } else { + dev_vdbg(codec->dev, "HPL connected to DAC\n"); + } + + reg = snd_soc_read(codec, WM8993_OUTPUT_MIXER2); + if (!(reg & WM8993_DACR_TO_HPOUT1R)) { + if (reg & ~WM8993_DACR_TO_MIXOUTR) { + dev_vdbg(codec->dev, "Analogue paths connected: %x\n", + reg & ~WM8993_DACR_TO_HPOUT1R); + return false; + } else { + dev_vdbg(codec->dev, "HPR connected to mixer\n"); + } + } else { + dev_vdbg(codec->dev, "HPR connected to DAC\n"); + } + + return true; +} + +struct wm_hubs_dcs_cache { + struct list_head list; + unsigned int left; + unsigned int right; + u16 dcs_cfg; +}; + +static bool wm_hubs_dcs_cache_get(struct snd_soc_codec *codec, + struct wm_hubs_dcs_cache **entry) +{ + struct wm_hubs_data *hubs = snd_soc_codec_get_drvdata(codec); + struct wm_hubs_dcs_cache *cache; + unsigned int left, right; + + left = snd_soc_read(codec, WM8993_LEFT_OUTPUT_VOLUME); + left &= WM8993_HPOUT1L_VOL_MASK; + + right = snd_soc_read(codec, WM8993_RIGHT_OUTPUT_VOLUME); + right &= WM8993_HPOUT1R_VOL_MASK; + + list_for_each_entry(cache, &hubs->dcs_cache, list) { + if (cache->left != left || cache->right != right) + continue; + + *entry = cache; + return true; + } + + return false; +} + +static void wm_hubs_dcs_cache_set(struct snd_soc_codec *codec, u16 dcs_cfg) +{ + struct wm_hubs_data *hubs = snd_soc_codec_get_drvdata(codec); + struct wm_hubs_dcs_cache *cache; + + if (hubs->no_cache_dac_hp_direct) + return; + + cache = devm_kzalloc(codec->dev, sizeof(*cache), GFP_KERNEL); + if (!cache) { + dev_err(codec->dev, "Failed to allocate DCS cache entry\n"); + return; + } + + cache->left = snd_soc_read(codec, WM8993_LEFT_OUTPUT_VOLUME); + cache->left &= WM8993_HPOUT1L_VOL_MASK; + + cache->right = snd_soc_read(codec, WM8993_RIGHT_OUTPUT_VOLUME); + cache->right &= WM8993_HPOUT1R_VOL_MASK; + + cache->dcs_cfg = dcs_cfg; + + list_add_tail(&cache->list, &hubs->dcs_cache); +} + /* * Startup calibration of the DC servo */ static void calibrate_dc_servo(struct snd_soc_codec *codec) { struct wm_hubs_data *hubs = snd_soc_codec_get_drvdata(codec); + struct wm_hubs_dcs_cache *cache; s8 offset; u16 reg, reg_l, reg_r, dcs_cfg, dcs_reg; @@ -129,10 +220,11 @@ static void calibrate_dc_servo(struct snd_soc_codec *codec) /* If we're using a digital only path and have a previously * callibrated DC servo offset stored then use that. */ - if (hubs->class_w && hubs->class_w_dcs) { - dev_dbg(codec->dev, "Using cached DC servo offset %x\n", - hubs->class_w_dcs); - snd_soc_write(codec, dcs_reg, hubs->class_w_dcs); + if (wm_hubs_dac_hp_direct(codec) && + wm_hubs_dcs_cache_get(codec, &cache)) { + dev_dbg(codec->dev, "Using cached DCS offset %x for %d,%d\n", + cache->dcs_cfg, cache->left, cache->right); + snd_soc_write(codec, dcs_reg, cache->dcs_cfg); wait_for_dc_servo(codec, WM8993_DCS_TRIG_DAC_WR_0 | WM8993_DCS_TRIG_DAC_WR_1); @@ -207,8 +299,8 @@ static void calibrate_dc_servo(struct snd_soc_codec *codec) /* Save the callibrated offset if we're in class W mode and * therefore don't have any analogue signal mixed in. */ - if (hubs->class_w && !hubs->no_cache_class_w) - hubs->class_w_dcs = dcs_cfg; + if (wm_hubs_dac_hp_direct(codec)) + wm_hubs_dcs_cache_set(codec, dcs_cfg); } /* @@ -223,9 +315,6 @@ static int wm8993_put_dc_servo(struct snd_kcontrol *kcontrol, ret = snd_soc_put_volsw(kcontrol, ucontrol); - /* Updating the analogue gains invalidates the DC servo cache */ - hubs->class_w_dcs = 0; - /* If we're applying an offset correction then updating the * callibration would be likely to introduce further offsets. */ if (hubs->dcs_codes_l || hubs->dcs_codes_r || hubs->no_series_update) @@ -530,6 +619,86 @@ static int lineout_event(struct snd_soc_dapm_widget *w, return 0; } +void wm_hubs_update_class_w(struct snd_soc_codec *codec) +{ + struct wm_hubs_data *hubs = snd_soc_codec_get_drvdata(codec); + int enable = WM8993_CP_DYN_V | WM8993_CP_DYN_FREQ; + + if (!wm_hubs_dac_hp_direct(codec)) + enable = false; + + if (hubs->check_class_w_digital && !hubs->check_class_w_digital(codec)) + enable = false; + + dev_vdbg(codec->dev, "Class W %s\n", enable ? "enabled" : "disabled"); + + snd_soc_update_bits(codec, WM8993_CLASS_W_0, + WM8993_CP_DYN_V | WM8993_CP_DYN_FREQ, enable); +} +EXPORT_SYMBOL_GPL(wm_hubs_update_class_w); + +#define WM_HUBS_SINGLE_W(xname, reg, shift, max, invert) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .info = snd_soc_info_volsw, \ + .get = snd_soc_dapm_get_volsw, .put = class_w_put_volsw, \ + .private_value = SOC_SINGLE_VALUE(reg, shift, max, invert) } + +static int class_w_put_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol); + struct snd_soc_dapm_widget *widget = wlist->widgets[0]; + struct snd_soc_codec *codec = widget->codec; + int ret; + + ret = snd_soc_dapm_put_volsw(kcontrol, ucontrol); + + wm_hubs_update_class_w(codec); + + return ret; +} + +#define WM_HUBS_ENUM_W(xname, xenum) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .info = snd_soc_info_enum_double, \ + .get = snd_soc_dapm_get_enum_double, \ + .put = class_w_put_double, \ + .private_value = (unsigned long)&xenum } + +static int class_w_put_double(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol); + struct snd_soc_dapm_widget *widget = wlist->widgets[0]; + struct snd_soc_codec *codec = widget->codec; + int ret; + + ret = snd_soc_dapm_put_enum_double(kcontrol, ucontrol); + + wm_hubs_update_class_w(codec); + + return ret; +} + +static const char *hp_mux_text[] = { + "Mixer", + "DAC", +}; + +static const struct soc_enum hpl_enum = + SOC_ENUM_SINGLE(WM8993_OUTPUT_MIXER1, 8, 2, hp_mux_text); + +const struct snd_kcontrol_new wm_hubs_hpl_mux = + WM_HUBS_ENUM_W("Left Headphone Mux", hpl_enum); +EXPORT_SYMBOL_GPL(wm_hubs_hpl_mux); + +static const struct soc_enum hpr_enum = + SOC_ENUM_SINGLE(WM8993_OUTPUT_MIXER2, 8, 2, hp_mux_text); + +const struct snd_kcontrol_new wm_hubs_hpr_mux = + WM_HUBS_ENUM_W("Right Headphone Mux", hpr_enum); +EXPORT_SYMBOL_GPL(wm_hubs_hpr_mux); + static const struct snd_kcontrol_new in1l_pga[] = { SOC_DAPM_SINGLE("IN1LP Switch", WM8993_INPUT_MIXER2, 5, 1, 0), SOC_DAPM_SINGLE("IN1LN Switch", WM8993_INPUT_MIXER2, 4, 1, 0), @@ -561,25 +730,25 @@ SOC_DAPM_SINGLE("IN1R Switch", WM8993_INPUT_MIXER4, 5, 1, 0), }; static const struct snd_kcontrol_new left_output_mixer[] = { -SOC_DAPM_SINGLE("Right Input Switch", WM8993_OUTPUT_MIXER1, 7, 1, 0), -SOC_DAPM_SINGLE("Left Input Switch", WM8993_OUTPUT_MIXER1, 6, 1, 0), -SOC_DAPM_SINGLE("IN2RN Switch", WM8993_OUTPUT_MIXER1, 5, 1, 0), -SOC_DAPM_SINGLE("IN2LN Switch", WM8993_OUTPUT_MIXER1, 4, 1, 0), -SOC_DAPM_SINGLE("IN2LP Switch", WM8993_OUTPUT_MIXER1, 1, 1, 0), -SOC_DAPM_SINGLE("IN1R Switch", WM8993_OUTPUT_MIXER1, 3, 1, 0), -SOC_DAPM_SINGLE("IN1L Switch", WM8993_OUTPUT_MIXER1, 2, 1, 0), -SOC_DAPM_SINGLE("DAC Switch", WM8993_OUTPUT_MIXER1, 0, 1, 0), +WM_HUBS_SINGLE_W("Right Input Switch", WM8993_OUTPUT_MIXER1, 7, 1, 0), +WM_HUBS_SINGLE_W("Left Input Switch", WM8993_OUTPUT_MIXER1, 6, 1, 0), +WM_HUBS_SINGLE_W("IN2RN Switch", WM8993_OUTPUT_MIXER1, 5, 1, 0), +WM_HUBS_SINGLE_W("IN2LN Switch", WM8993_OUTPUT_MIXER1, 4, 1, 0), +WM_HUBS_SINGLE_W("IN2LP Switch", WM8993_OUTPUT_MIXER1, 1, 1, 0), +WM_HUBS_SINGLE_W("IN1R Switch", WM8993_OUTPUT_MIXER1, 3, 1, 0), +WM_HUBS_SINGLE_W("IN1L Switch", WM8993_OUTPUT_MIXER1, 2, 1, 0), +WM_HUBS_SINGLE_W("DAC Switch", WM8993_OUTPUT_MIXER1, 0, 1, 0), }; static const struct snd_kcontrol_new right_output_mixer[] = { -SOC_DAPM_SINGLE("Left Input Switch", WM8993_OUTPUT_MIXER2, 7, 1, 0), -SOC_DAPM_SINGLE("Right Input Switch", WM8993_OUTPUT_MIXER2, 6, 1, 0), -SOC_DAPM_SINGLE("IN2LN Switch", WM8993_OUTPUT_MIXER2, 5, 1, 0), -SOC_DAPM_SINGLE("IN2RN Switch", WM8993_OUTPUT_MIXER2, 4, 1, 0), -SOC_DAPM_SINGLE("IN1L Switch", WM8993_OUTPUT_MIXER2, 3, 1, 0), -SOC_DAPM_SINGLE("IN1R Switch", WM8993_OUTPUT_MIXER2, 2, 1, 0), -SOC_DAPM_SINGLE("IN2RP Switch", WM8993_OUTPUT_MIXER2, 1, 1, 0), -SOC_DAPM_SINGLE("DAC Switch", WM8993_OUTPUT_MIXER2, 0, 1, 0), +WM_HUBS_SINGLE_W("Left Input Switch", WM8993_OUTPUT_MIXER2, 7, 1, 0), +WM_HUBS_SINGLE_W("Right Input Switch", WM8993_OUTPUT_MIXER2, 6, 1, 0), +WM_HUBS_SINGLE_W("IN2LN Switch", WM8993_OUTPUT_MIXER2, 5, 1, 0), +WM_HUBS_SINGLE_W("IN2RN Switch", WM8993_OUTPUT_MIXER2, 4, 1, 0), +WM_HUBS_SINGLE_W("IN1L Switch", WM8993_OUTPUT_MIXER2, 3, 1, 0), +WM_HUBS_SINGLE_W("IN1R Switch", WM8993_OUTPUT_MIXER2, 2, 1, 0), +WM_HUBS_SINGLE_W("IN2RP Switch", WM8993_OUTPUT_MIXER2, 1, 1, 0), +WM_HUBS_SINGLE_W("DAC Switch", WM8993_OUTPUT_MIXER2, 0, 1, 0), }; static const struct snd_kcontrol_new earpiece_mixer[] = { @@ -943,6 +1112,7 @@ int wm_hubs_add_analogue_routes(struct snd_soc_codec *codec, struct wm_hubs_data *hubs = snd_soc_codec_get_drvdata(codec); struct snd_soc_dapm_context *dapm = &codec->dapm; + INIT_LIST_HEAD(&hubs->dcs_cache); init_completion(&hubs->dcs_done); snd_soc_dapm_add_routes(dapm, analogue_routes, diff --git a/sound/soc/codecs/wm_hubs.h b/sound/soc/codecs/wm_hubs.h index 5705276f494..da2dc899ce6 100644 --- a/sound/soc/codecs/wm_hubs.h +++ b/sound/soc/codecs/wm_hubs.h @@ -16,6 +16,8 @@ #include <linux/completion.h> #include <linux/interrupt.h> +#include <linux/list.h> +#include <sound/control.h> struct snd_soc_codec; @@ -30,9 +32,9 @@ struct wm_hubs_data { int series_startup; int no_series_update; - bool no_cache_class_w; - bool class_w; - u16 class_w_dcs; + bool no_cache_dac_hp_direct; + struct list_head dcs_cache; + bool (*check_class_w_digital)(struct snd_soc_codec *); bool lineout1_se; bool lineout1n_ena; @@ -58,5 +60,9 @@ extern irqreturn_t wm_hubs_dcs_done(int irq, void *data); extern void wm_hubs_vmid_ena(struct snd_soc_codec *codec); extern void wm_hubs_set_bias_level(struct snd_soc_codec *codec, enum snd_soc_bias_level level); +extern void wm_hubs_update_class_w(struct snd_soc_codec *codec); + +extern const struct snd_kcontrol_new wm_hubs_hpl_mux; +extern const struct snd_kcontrol_new wm_hubs_hpr_mux; #endif diff --git a/sound/soc/ep93xx/ep93xx-ac97.c b/sound/soc/ep93xx/ep93xx-ac97.c index 0678637abd6..bdffab33e16 100644 --- a/sound/soc/ep93xx/ep93xx-ac97.c +++ b/sound/soc/ep93xx/ep93xx-ac97.c @@ -87,17 +87,13 @@ * struct ep93xx_ac97_info - EP93xx AC97 controller info structure * @lock: mutex serializing access to the bus (slot 1 & 2 ops) * @dev: pointer to the platform device dev structure - * @mem: physical memory resource for the registers * @regs: mapped AC97 controller registers - * @irq: AC97 interrupt number * @done: bus ops wait here for an interrupt */ struct ep93xx_ac97_info { struct mutex lock; struct device *dev; - struct resource *mem; void __iomem *regs; - int irq; struct completion done; }; @@ -359,66 +355,50 @@ static struct snd_soc_dai_driver ep93xx_ac97_dai = { static int __devinit ep93xx_ac97_probe(struct platform_device *pdev) { struct ep93xx_ac97_info *info; + struct resource *res; + unsigned int irq; int ret; - info = kzalloc(sizeof(struct ep93xx_ac97_info), GFP_KERNEL); + info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); if (!info) return -ENOMEM; - dev_set_drvdata(&pdev->dev, info); - - mutex_init(&info->lock); - init_completion(&info->done); - info->dev = &pdev->dev; + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENODEV; - info->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!info->mem) { - ret = -ENXIO; - goto fail_free_info; - } + info->regs = devm_request_and_ioremap(&pdev->dev, res); + if (!info->regs) + return -ENXIO; - info->irq = platform_get_irq(pdev, 0); - if (!info->irq) { - ret = -ENXIO; - goto fail_free_info; - } + irq = platform_get_irq(pdev, 0); + if (!irq) + return -ENODEV; - if (!request_mem_region(info->mem->start, resource_size(info->mem), - pdev->name)) { - ret = -EBUSY; - goto fail_free_info; - } + ret = devm_request_irq(&pdev->dev, irq, ep93xx_ac97_interrupt, + IRQF_TRIGGER_HIGH, pdev->name, info); + if (ret) + goto fail; - info->regs = ioremap(info->mem->start, resource_size(info->mem)); - if (!info->regs) { - ret = -ENOMEM; - goto fail_release_mem; - } + dev_set_drvdata(&pdev->dev, info); - ret = request_irq(info->irq, ep93xx_ac97_interrupt, IRQF_TRIGGER_HIGH, - pdev->name, info); - if (ret) - goto fail_unmap_mem; + mutex_init(&info->lock); + init_completion(&info->done); + info->dev = &pdev->dev; ep93xx_ac97_info = info; platform_set_drvdata(pdev, info); ret = snd_soc_register_dai(&pdev->dev, &ep93xx_ac97_dai); if (ret) - goto fail_free_irq; + goto fail; return 0; -fail_free_irq: +fail: platform_set_drvdata(pdev, NULL); - free_irq(info->irq, info); -fail_unmap_mem: - iounmap(info->regs); -fail_release_mem: - release_mem_region(info->mem->start, resource_size(info->mem)); -fail_free_info: - kfree(info); - + ep93xx_ac97_info = NULL; + dev_set_drvdata(&pdev->dev, NULL); return ret; } @@ -431,11 +411,9 @@ static int __devexit ep93xx_ac97_remove(struct platform_device *pdev) /* disable the AC97 controller */ ep93xx_ac97_write_reg(info, AC97GCR, 0); - free_irq(info->irq, info); - iounmap(info->regs); - release_mem_region(info->mem->start, resource_size(info->mem)); platform_set_drvdata(pdev, NULL); - kfree(info); + ep93xx_ac97_info = NULL; + dev_set_drvdata(&pdev->dev, NULL); return 0; } diff --git a/sound/soc/ep93xx/ep93xx-i2s.c b/sound/soc/ep93xx/ep93xx-i2s.c index f7a62348e3f..8df8f6dc474 100644 --- a/sound/soc/ep93xx/ep93xx-i2s.c +++ b/sound/soc/ep93xx/ep93xx-i2s.c @@ -63,7 +63,6 @@ struct ep93xx_i2s_info { struct clk *sclk; struct clk *lrclk; struct ep93xx_pcm_dma_params *dma_params; - struct resource *mem; void __iomem *regs; }; @@ -373,38 +372,22 @@ static int ep93xx_i2s_probe(struct platform_device *pdev) struct resource *res; int err; - info = kzalloc(sizeof(struct ep93xx_i2s_info), GFP_KERNEL); - if (!info) { - err = -ENOMEM; - goto fail; - } - - dev_set_drvdata(&pdev->dev, info); - info->dma_params = ep93xx_i2s_dma_params; + info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!res) { - err = -ENODEV; - goto fail_free_info; - } + if (!res) + return -ENODEV; - info->mem = request_mem_region(res->start, resource_size(res), - pdev->name); - if (!info->mem) { - err = -EBUSY; - goto fail_free_info; - } - - info->regs = ioremap(info->mem->start, resource_size(info->mem)); - if (!info->regs) { - err = -ENXIO; - goto fail_release_mem; - } + info->regs = devm_request_and_ioremap(&pdev->dev, res); + if (!info->regs) + return -ENXIO; info->mclk = clk_get(&pdev->dev, "mclk"); if (IS_ERR(info->mclk)) { err = PTR_ERR(info->mclk); - goto fail_unmap_mem; + goto fail; } info->sclk = clk_get(&pdev->dev, "sclk"); @@ -419,6 +402,9 @@ static int ep93xx_i2s_probe(struct platform_device *pdev) goto fail_put_sclk; } + dev_set_drvdata(&pdev->dev, info); + info->dma_params = ep93xx_i2s_dma_params; + err = snd_soc_register_dai(&pdev->dev, &ep93xx_i2s_dai); if (err) goto fail_put_lrclk; @@ -426,17 +412,12 @@ static int ep93xx_i2s_probe(struct platform_device *pdev) return 0; fail_put_lrclk: + dev_set_drvdata(&pdev->dev, NULL); clk_put(info->lrclk); fail_put_sclk: clk_put(info->sclk); fail_put_mclk: clk_put(info->mclk); -fail_unmap_mem: - iounmap(info->regs); -fail_release_mem: - release_mem_region(info->mem->start, resource_size(info->mem)); -fail_free_info: - kfree(info); fail: return err; } @@ -446,12 +427,10 @@ static int __devexit ep93xx_i2s_remove(struct platform_device *pdev) struct ep93xx_i2s_info *info = dev_get_drvdata(&pdev->dev); snd_soc_unregister_dai(&pdev->dev); + dev_set_drvdata(&pdev->dev, NULL); clk_put(info->lrclk); clk_put(info->sclk); clk_put(info->mclk); - iounmap(info->regs); - release_mem_region(info->mem->start, resource_size(info->mem)); - kfree(info); return 0; } diff --git a/sound/soc/fsl/Kconfig b/sound/soc/fsl/Kconfig index d754d34d68a..d70133086ac 100644 --- a/sound/soc/fsl/Kconfig +++ b/sound/soc/fsl/Kconfig @@ -1,18 +1,31 @@ -config SND_MPC52xx_DMA +config SND_SOC_FSL_SSI tristate -# ASoC platform support for the Freescale PowerPC SOCs that have an SSI and -# an Elo DMA controller, such as the MPC8610 and P1022. You will still need to -# select a platform driver and a codec driver. -config SND_SOC_POWERPC_SSI +config SND_SOC_FSL_UTILS tristate + +menuconfig SND_POWERPC_SOC + tristate "SoC Audio for Freescale PowerPC CPUs" depends on FSL_SOC + help + Say Y or M if you want to add support for codecs attached to + the PowerPC CPUs. + +if SND_POWERPC_SOC + +config SND_MPC52xx_DMA + tristate + +config SND_SOC_POWERPC_DMA + tristate config SND_SOC_MPC8610_HPCD tristate "ALSA SoC support for the Freescale MPC8610 HPCD board" # I2C is necessary for the CS4270 driver depends on MPC8610_HPCD && I2C - select SND_SOC_POWERPC_SSI + select SND_SOC_FSL_SSI + select SND_SOC_FSL_UTILS + select SND_SOC_POWERPC_DMA select SND_SOC_CS4270 select SND_SOC_CS4270_VD33_ERRATA default y if MPC8610_HPCD @@ -23,7 +36,9 @@ config SND_SOC_P1022_DS tristate "ALSA SoC support for the Freescale P1022 DS board" # I2C is necessary for the WM8776 driver depends on P1022_DS && I2C - select SND_SOC_POWERPC_SSI + select SND_SOC_FSL_SSI + select SND_SOC_FSL_UTILS + select SND_SOC_POWERPC_DMA select SND_SOC_WM8776 default y if P1022_DS help @@ -65,3 +80,103 @@ config SND_MPC52xx_SOC_EFIKA help Say Y if you want to add support for sound on the Efika. +endif # SND_POWERPC_SOC + +menuconfig SND_IMX_SOC + tristate "SoC Audio for Freescale i.MX CPUs" + depends on ARCH_MXC + help + Say Y or M if you want to add support for codecs attached to + the i.MX CPUs. + +if SND_IMX_SOC + +config SND_SOC_IMX_SSI + tristate + +config SND_SOC_IMX_PCM + tristate + +config SND_SOC_IMX_PCM_FIQ + tristate + select FIQ + select SND_SOC_IMX_PCM + +config SND_SOC_IMX_PCM_DMA + tristate + select SND_SOC_DMAENGINE_PCM + select SND_SOC_IMX_PCM + +config SND_SOC_IMX_AUDMUX + tristate + +config SND_MXC_SOC_WM1133_EV1 + tristate "Audio on the i.MX31ADS with WM1133-EV1 fitted" + depends on MACH_MX31ADS_WM1133_EV1 && EXPERIMENTAL + select SND_SOC_WM8350 + select SND_SOC_IMX_PCM_FIQ + select SND_SOC_IMX_AUDMUX + select SND_SOC_IMX_SSI + help + Enable support for audio on the i.MX31ADS with the WM1133-EV1 + PMIC board with WM8835x fitted. + +config SND_SOC_MX27VIS_AIC32X4 + tristate "SoC audio support for Visstrim M10 boards" + depends on MACH_IMX27_VISSTRIM_M10 && I2C + select SND_SOC_TLV320AIC32X4 + select SND_SOC_IMX_PCM_DMA + select SND_SOC_IMX_AUDMUX + select SND_SOC_IMX_SSI + help + Say Y if you want to add support for SoC audio on Visstrim SM10 + board with TLV320AIC32X4 codec. + +config SND_SOC_PHYCORE_AC97 + tristate "SoC Audio support for Phytec phyCORE (and phyCARD) boards" + depends on MACH_PCM043 || MACH_PCA100 + select SND_SOC_AC97_BUS + select SND_SOC_WM9712 + select SND_SOC_IMX_PCM_FIQ + select SND_SOC_IMX_AUDMUX + select SND_SOC_IMX_SSI + help + Say Y if you want to add support for SoC audio on Phytec phyCORE + and phyCARD boards in AC97 mode + +config SND_SOC_EUKREA_TLV320 + tristate "Eukrea TLV320" + depends on MACH_EUKREA_MBIMX27_BASEBOARD \ + || MACH_EUKREA_MBIMXSD25_BASEBOARD \ + || MACH_EUKREA_MBIMXSD35_BASEBOARD \ + || MACH_EUKREA_MBIMXSD51_BASEBOARD + depends on I2C + select SND_SOC_TLV320AIC23 + select SND_SOC_IMX_PCM_FIQ + select SND_SOC_IMX_AUDMUX + select SND_SOC_IMX_SSI + help + Enable I2S based access to the TLV320AIC23B codec attached + to the SSI interface + +config SND_SOC_IMX_SGTL5000 + tristate "SoC Audio support for i.MX boards with sgtl5000" + depends on OF && I2C + select SND_SOC_SGTL5000 + select SND_SOC_IMX_PCM_DMA + select SND_SOC_IMX_AUDMUX + select SND_SOC_FSL_SSI + select SND_SOC_FSL_UTILS + help + Say Y if you want to add support for SoC audio on an i.MX board with + a sgtl5000 codec. + +config SND_SOC_IMX_MC13783 + tristate "SoC Audio support for I.MX boards with mc13783" + depends on MFD_MC13783 + select SND_SOC_IMX_SSI + select SND_SOC_IMX_AUDMUX + select SND_SOC_MC13783 + select SND_SOC_IMX_PCM_DMA + +endif # SND_IMX_SOC diff --git a/sound/soc/fsl/Makefile b/sound/soc/fsl/Makefile index b4a38c0ac58..5f3cf3f52ea 100644 --- a/sound/soc/fsl/Makefile +++ b/sound/soc/fsl/Makefile @@ -8,8 +8,11 @@ obj-$(CONFIG_SND_SOC_P1022_DS) += snd-soc-p1022-ds.o # Freescale PowerPC SSI/DMA Platform Support snd-soc-fsl-ssi-objs := fsl_ssi.o +snd-soc-fsl-utils-objs := fsl_utils.o snd-soc-fsl-dma-objs := fsl_dma.o -obj-$(CONFIG_SND_SOC_POWERPC_SSI) += snd-soc-fsl-ssi.o snd-soc-fsl-dma.o +obj-$(CONFIG_SND_SOC_FSL_SSI) += snd-soc-fsl-ssi.o +obj-$(CONFIG_SND_SOC_FSL_UTILS) += snd-soc-fsl-utils.o +obj-$(CONFIG_SND_SOC_POWERPC_DMA) += snd-soc-fsl-dma.o # MPC5200 Platform Support obj-$(CONFIG_SND_MPC52xx_DMA) += mpc5200_dma.o @@ -20,3 +23,29 @@ obj-$(CONFIG_SND_SOC_MPC5200_AC97) += mpc5200_psc_ac97.o obj-$(CONFIG_SND_MPC52xx_SOC_PCM030) += pcm030-audio-fabric.o obj-$(CONFIG_SND_MPC52xx_SOC_EFIKA) += efika-audio-fabric.o +# i.MX Platform Support +snd-soc-imx-ssi-objs := imx-ssi.o +snd-soc-imx-audmux-objs := imx-audmux.o + +obj-$(CONFIG_SND_SOC_IMX_SSI) += snd-soc-imx-ssi.o +obj-$(CONFIG_SND_SOC_IMX_AUDMUX) += snd-soc-imx-audmux.o + +obj-$(CONFIG_SND_SOC_IMX_PCM) += snd-soc-imx-pcm.o +snd-soc-imx-pcm-y := imx-pcm.o +snd-soc-imx-pcm-$(CONFIG_SND_SOC_IMX_PCM_FIQ) += imx-pcm-fiq.o +snd-soc-imx-pcm-$(CONFIG_SND_SOC_IMX_PCM_DMA) += imx-pcm-dma.o + +# i.MX Machine Support +snd-soc-eukrea-tlv320-objs := eukrea-tlv320.o +snd-soc-phycore-ac97-objs := phycore-ac97.o +snd-soc-mx27vis-aic32x4-objs := mx27vis-aic32x4.o +snd-soc-wm1133-ev1-objs := wm1133-ev1.o +snd-soc-imx-sgtl5000-objs := imx-sgtl5000.o +snd-soc-imx-mc13783-objs := imx-mc13783.o + +obj-$(CONFIG_SND_SOC_EUKREA_TLV320) += snd-soc-eukrea-tlv320.o +obj-$(CONFIG_SND_SOC_PHYCORE_AC97) += snd-soc-phycore-ac97.o +obj-$(CONFIG_SND_SOC_MX27VIS_AIC32X4) += snd-soc-mx27vis-aic32x4.o +obj-$(CONFIG_SND_MXC_SOC_WM1133_EV1) += snd-soc-wm1133-ev1.o +obj-$(CONFIG_SND_SOC_IMX_SGTL5000) += snd-soc-imx-sgtl5000.o +obj-$(CONFIG_SND_SOC_IMX_MC13783) += snd-soc-imx-mc13783.o diff --git a/sound/soc/imx/eukrea-tlv320.c b/sound/soc/fsl/eukrea-tlv320.c index 7d4475cfdb2..efb9ede0120 100644 --- a/sound/soc/imx/eukrea-tlv320.c +++ b/sound/soc/fsl/eukrea-tlv320.c @@ -7,7 +7,7 @@ * which is Copyright 2009 Simtec Electronics * and on sound/soc/imx/phycore-ac97.c which is * Copyright 2009 Sascha Hauer, Pengutronix <s.hauer@pengutronix.de> - * + * * 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 diff --git a/sound/soc/fsl/fsl_ssi.c b/sound/soc/fsl/fsl_ssi.c index 2eb407fa3b4..4ed2afd4778 100644 --- a/sound/soc/fsl/fsl_ssi.c +++ b/sound/soc/fsl/fsl_ssi.c @@ -11,11 +11,15 @@ */ #include <linux/init.h> +#include <linux/io.h> #include <linux/module.h> #include <linux/interrupt.h> +#include <linux/clk.h> #include <linux/device.h> #include <linux/delay.h> #include <linux/slab.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> #include <linux/of_platform.h> #include <sound/core.h> @@ -25,6 +29,26 @@ #include <sound/soc.h> #include "fsl_ssi.h" +#include "imx-pcm.h" + +#ifdef PPC +#define read_ssi(addr) in_be32(addr) +#define write_ssi(val, addr) out_be32(addr, val) +#define write_ssi_mask(addr, clear, set) clrsetbits_be32(addr, clear, set) +#elif defined ARM +#define read_ssi(addr) readl(addr) +#define write_ssi(val, addr) writel(val, addr) +/* + * FIXME: Proper locking should be added at write_ssi_mask caller level + * to ensure this register read/modify/write sequence is race free. + */ +static inline void write_ssi_mask(u32 __iomem *addr, u32 clear, u32 set) +{ + u32 val = readl(addr); + val = (val & ~clear) | set; + writel(val, addr); +} +#endif /** * FSLSSI_I2S_RATES: sample rates supported by the I2S @@ -94,6 +118,13 @@ struct fsl_ssi_private { struct device_attribute dev_attr; struct platform_device *pdev; + bool new_binding; + bool ssi_on_imx; + struct clk *clk; + struct platform_device *imx_pcm_pdev; + struct imx_pcm_dma_params dma_params_tx; + struct imx_pcm_dma_params dma_params_rx; + struct { unsigned int rfrc; unsigned int tfrc; @@ -145,7 +176,7 @@ static irqreturn_t fsl_ssi_isr(int irq, void *dev_id) were interrupted for. We mask it with the Interrupt Enable register so that we only check for events that we're interested in. */ - sisr = in_be32(&ssi->sisr) & SIER_FLAGS; + sisr = read_ssi(&ssi->sisr) & SIER_FLAGS; if (sisr & CCSR_SSI_SISR_RFRC) { ssi_private->stats.rfrc++; @@ -260,7 +291,7 @@ static irqreturn_t fsl_ssi_isr(int irq, void *dev_id) /* Clear the bits that we set */ if (sisr2) - out_be32(&ssi->sisr, sisr2); + write_ssi(sisr2, &ssi->sisr); return ret; } @@ -295,7 +326,7 @@ static int fsl_ssi_startup(struct snd_pcm_substream *substream, * SSI needs to be disabled before updating the registers we set * here. */ - clrbits32(&ssi->scr, CCSR_SSI_SCR_SSIEN); + write_ssi_mask(&ssi->scr, CCSR_SSI_SCR_SSIEN, 0); /* * Program the SSI into I2S Slave Non-Network Synchronous mode. @@ -303,20 +334,18 @@ static int fsl_ssi_startup(struct snd_pcm_substream *substream, * * FIXME: Little-endian samples require a different shift dir */ - clrsetbits_be32(&ssi->scr, + write_ssi_mask(&ssi->scr, CCSR_SSI_SCR_I2S_MODE_MASK | CCSR_SSI_SCR_SYN, CCSR_SSI_SCR_TFR_CLK_DIS | CCSR_SSI_SCR_I2S_MODE_SLAVE | (synchronous ? CCSR_SSI_SCR_SYN : 0)); - out_be32(&ssi->stcr, - CCSR_SSI_STCR_TXBIT0 | CCSR_SSI_STCR_TFEN0 | + write_ssi(CCSR_SSI_STCR_TXBIT0 | CCSR_SSI_STCR_TFEN0 | CCSR_SSI_STCR_TFSI | CCSR_SSI_STCR_TEFS | - CCSR_SSI_STCR_TSCKP); + CCSR_SSI_STCR_TSCKP, &ssi->stcr); - out_be32(&ssi->srcr, - CCSR_SSI_SRCR_RXBIT0 | CCSR_SSI_SRCR_RFEN0 | + write_ssi(CCSR_SSI_SRCR_RXBIT0 | CCSR_SSI_SRCR_RFEN0 | CCSR_SSI_SRCR_RFSI | CCSR_SSI_SRCR_REFS | - CCSR_SSI_SRCR_RSCKP); + CCSR_SSI_SRCR_RSCKP, &ssi->srcr); /* * The DC and PM bits are only used if the SSI is the clock @@ -324,7 +353,7 @@ static int fsl_ssi_startup(struct snd_pcm_substream *substream, */ /* Enable the interrupts and DMA requests */ - out_be32(&ssi->sier, SIER_FLAGS); + write_ssi(SIER_FLAGS, &ssi->sier); /* * Set the watermark for transmit FIFI 0 and receive FIFO 0. We @@ -339,9 +368,9 @@ static int fsl_ssi_startup(struct snd_pcm_substream *substream, * make this value larger (and maybe we should), but this way * data will be written to memory as soon as it's available. */ - out_be32(&ssi->sfcsr, - CCSR_SSI_SFCSR_TFWM0(ssi_private->fifo_depth - 2) | - CCSR_SSI_SFCSR_RFWM0(ssi_private->fifo_depth - 2)); + write_ssi(CCSR_SSI_SFCSR_TFWM0(ssi_private->fifo_depth - 2) | + CCSR_SSI_SFCSR_RFWM0(ssi_private->fifo_depth - 2), + &ssi->sfcsr); /* * We keep the SSI disabled because if we enable it, then the @@ -393,6 +422,12 @@ static int fsl_ssi_startup(struct snd_pcm_substream *substream, ssi_private->second_stream = substream; } + if (ssi_private->ssi_on_imx) + snd_soc_dai_set_dma_data(dai, substream, + (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ? + &ssi_private->dma_params_tx : + &ssi_private->dma_params_rx); + return 0; } @@ -417,7 +452,7 @@ static int fsl_ssi_hw_params(struct snd_pcm_substream *substream, unsigned int sample_size = snd_pcm_format_width(params_format(hw_params)); u32 wl = CCSR_SSI_SxCCR_WL(sample_size); - int enabled = in_be32(&ssi->scr) & CCSR_SSI_SCR_SSIEN; + int enabled = read_ssi(&ssi->scr) & CCSR_SSI_SCR_SSIEN; /* * If we're in synchronous mode, and the SSI is already enabled, @@ -439,9 +474,9 @@ static int fsl_ssi_hw_params(struct snd_pcm_substream *substream, /* In synchronous mode, the SSI uses STCCR for capture */ if ((substream->stream == SNDRV_PCM_STREAM_PLAYBACK) || ssi_private->cpu_dai_drv.symmetric_rates) - clrsetbits_be32(&ssi->stccr, CCSR_SSI_SxCCR_WL_MASK, wl); + write_ssi_mask(&ssi->stccr, CCSR_SSI_SxCCR_WL_MASK, wl); else - clrsetbits_be32(&ssi->srccr, CCSR_SSI_SxCCR_WL_MASK, wl); + write_ssi_mask(&ssi->srccr, CCSR_SSI_SxCCR_WL_MASK, wl); return 0; } @@ -466,19 +501,19 @@ static int fsl_ssi_trigger(struct snd_pcm_substream *substream, int cmd, case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - setbits32(&ssi->scr, + write_ssi_mask(&ssi->scr, 0, CCSR_SSI_SCR_SSIEN | CCSR_SSI_SCR_TE); else - setbits32(&ssi->scr, + write_ssi_mask(&ssi->scr, 0, CCSR_SSI_SCR_SSIEN | CCSR_SSI_SCR_RE); break; case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_PAUSE_PUSH: if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - clrbits32(&ssi->scr, CCSR_SSI_SCR_TE); + write_ssi_mask(&ssi->scr, CCSR_SSI_SCR_TE, 0); else - clrbits32(&ssi->scr, CCSR_SSI_SCR_RE); + write_ssi_mask(&ssi->scr, CCSR_SSI_SCR_RE, 0); break; default: @@ -510,7 +545,7 @@ static void fsl_ssi_shutdown(struct snd_pcm_substream *substream, if (!ssi_private->first_stream) { struct ccsr_ssi __iomem *ssi = ssi_private->ssi; - clrbits32(&ssi->scr, CCSR_SSI_SCR_SSIEN); + write_ssi_mask(&ssi->scr, CCSR_SSI_SCR_SSIEN, 0); } } @@ -622,12 +657,6 @@ static int __devinit fsl_ssi_probe(struct platform_device *pdev) if (!of_device_is_available(np)) return -ENODEV; - /* Check for a codec-handle property. */ - if (!of_get_property(np, "codec-handle", NULL)) { - dev_err(&pdev->dev, "missing codec-handle property\n"); - return -ENODEV; - } - /* We only support the SSI in "I2S Slave" mode */ sprop = of_get_property(np, "fsl,mode", NULL); if (!sprop || strcmp(sprop, "i2s-slave")) { @@ -692,6 +721,50 @@ static int __devinit fsl_ssi_probe(struct platform_device *pdev) /* Older 8610 DTs didn't have the fifo-depth property */ ssi_private->fifo_depth = 8; + if (of_device_is_compatible(pdev->dev.of_node, "fsl,imx21-ssi")) { + u32 dma_events[2]; + ssi_private->ssi_on_imx = true; + + ssi_private->clk = clk_get(&pdev->dev, NULL); + if (IS_ERR(ssi_private->clk)) { + ret = PTR_ERR(ssi_private->clk); + dev_err(&pdev->dev, "could not get clock: %d\n", ret); + goto error_irq; + } + clk_prepare_enable(ssi_private->clk); + + /* + * We have burstsize be "fifo_depth - 2" to match the SSI + * watermark setting in fsl_ssi_startup(). + */ + ssi_private->dma_params_tx.burstsize = + ssi_private->fifo_depth - 2; + ssi_private->dma_params_rx.burstsize = + ssi_private->fifo_depth - 2; + ssi_private->dma_params_tx.dma_addr = + ssi_private->ssi_phys + offsetof(struct ccsr_ssi, stx0); + ssi_private->dma_params_rx.dma_addr = + ssi_private->ssi_phys + offsetof(struct ccsr_ssi, srx0); + /* + * TODO: This is a temporary solution and should be changed + * to use generic DMA binding later when the helplers get in. + */ + ret = of_property_read_u32_array(pdev->dev.of_node, + "fsl,ssi-dma-events", dma_events, 2); + if (ret) { + dev_err(&pdev->dev, "could not get dma events\n"); + goto error_clk; + } + ssi_private->dma_params_tx.dma = dma_events[0]; + ssi_private->dma_params_rx.dma = dma_events[1]; + + ssi_private->dma_params_tx.shared_peripheral = + of_device_is_compatible(of_get_parent(np), + "fsl,spba-bus"); + ssi_private->dma_params_rx.shared_peripheral = + ssi_private->dma_params_tx.shared_peripheral; + } + /* Initialize the the device_attribute structure */ dev_attr = &ssi_private->dev_attr; sysfs_attr_init(&dev_attr->attr); @@ -715,6 +788,26 @@ static int __devinit fsl_ssi_probe(struct platform_device *pdev) goto error_dev; } + if (ssi_private->ssi_on_imx) { + ssi_private->imx_pcm_pdev = + platform_device_register_simple("imx-pcm-audio", + -1, NULL, 0); + if (IS_ERR(ssi_private->imx_pcm_pdev)) { + ret = PTR_ERR(ssi_private->imx_pcm_pdev); + goto error_dev; + } + } + + /* + * If codec-handle property is missing from SSI node, we assume + * that the machine driver uses new binding which does not require + * SSI driver to trigger machine driver's probe. + */ + if (!of_get_property(np, "codec-handle", NULL)) { + ssi_private->new_binding = true; + goto done; + } + /* Trigger the machine driver's probe function. The platform driver * name of the machine driver is taken from /compatible property of the * device tree. We also pass the address of the CPU DAI driver @@ -736,15 +829,24 @@ static int __devinit fsl_ssi_probe(struct platform_device *pdev) goto error_dai; } +done: return 0; error_dai: + if (ssi_private->ssi_on_imx) + platform_device_unregister(ssi_private->imx_pcm_pdev); snd_soc_unregister_dai(&pdev->dev); error_dev: dev_set_drvdata(&pdev->dev, NULL); device_remove_file(&pdev->dev, dev_attr); +error_clk: + if (ssi_private->ssi_on_imx) { + clk_disable_unprepare(ssi_private->clk); + clk_put(ssi_private->clk); + } + error_irq: free_irq(ssi_private->irq, ssi_private); @@ -764,7 +866,13 @@ static int fsl_ssi_remove(struct platform_device *pdev) { struct fsl_ssi_private *ssi_private = dev_get_drvdata(&pdev->dev); - platform_device_unregister(ssi_private->pdev); + if (!ssi_private->new_binding) + platform_device_unregister(ssi_private->pdev); + if (ssi_private->ssi_on_imx) { + platform_device_unregister(ssi_private->imx_pcm_pdev); + clk_disable_unprepare(ssi_private->clk); + clk_put(ssi_private->clk); + } snd_soc_unregister_dai(&pdev->dev); device_remove_file(&pdev->dev, &ssi_private->dev_attr); @@ -779,6 +887,7 @@ static int fsl_ssi_remove(struct platform_device *pdev) static const struct of_device_id fsl_ssi_ids[] = { { .compatible = "fsl,mpc8610-ssi", }, + { .compatible = "fsl,imx21-ssi", }, {} }; MODULE_DEVICE_TABLE(of, fsl_ssi_ids); diff --git a/sound/soc/fsl/fsl_utils.c b/sound/soc/fsl/fsl_utils.c new file mode 100644 index 00000000000..b9e42b503a3 --- /dev/null +++ b/sound/soc/fsl/fsl_utils.c @@ -0,0 +1,91 @@ +/** + * Freescale ALSA SoC Machine driver utility + * + * Author: Timur Tabi <timur@freescale.com> + * + * Copyright 2010 Freescale Semiconductor, Inc. + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ + +#include <linux/module.h> +#include <linux/of_address.h> +#include <sound/soc.h> + +#include "fsl_utils.h" + +/** + * fsl_asoc_get_dma_channel - determine the dma channel for a SSI node + * + * @ssi_np: pointer to the SSI device tree node + * @name: name of the phandle pointing to the dma channel + * @dai: ASoC DAI link pointer to be filled with platform_name + * @dma_channel_id: dma channel id to be returned + * @dma_id: dma id to be returned + * + * This function determines the dma and channel id for given SSI node. It + * also discovers the platform_name for the ASoC DAI link. + */ +int fsl_asoc_get_dma_channel(struct device_node *ssi_np, + const char *name, + struct snd_soc_dai_link *dai, + unsigned int *dma_channel_id, + unsigned int *dma_id) +{ + struct resource res; + struct device_node *dma_channel_np, *dma_np; + const u32 *iprop; + int ret; + + dma_channel_np = of_parse_phandle(ssi_np, name, 0); + if (!dma_channel_np) + return -EINVAL; + + if (!of_device_is_compatible(dma_channel_np, "fsl,ssi-dma-channel")) { + of_node_put(dma_channel_np); + return -EINVAL; + } + + /* Determine the dev_name for the device_node. This code mimics the + * behavior of of_device_make_bus_id(). We need this because ASoC uses + * the dev_name() of the device to match the platform (DMA) device with + * the CPU (SSI) device. It's all ugly and hackish, but it works (for + * now). + * + * dai->platform name should already point to an allocated buffer. + */ + ret = of_address_to_resource(dma_channel_np, 0, &res); + if (ret) { + of_node_put(dma_channel_np); + return ret; + } + snprintf((char *)dai->platform_name, DAI_NAME_SIZE, "%llx.%s", + (unsigned long long) res.start, dma_channel_np->name); + + iprop = of_get_property(dma_channel_np, "cell-index", NULL); + if (!iprop) { + of_node_put(dma_channel_np); + return -EINVAL; + } + *dma_channel_id = be32_to_cpup(iprop); + + dma_np = of_get_parent(dma_channel_np); + iprop = of_get_property(dma_np, "cell-index", NULL); + if (!iprop) { + of_node_put(dma_np); + return -EINVAL; + } + *dma_id = be32_to_cpup(iprop); + + of_node_put(dma_np); + of_node_put(dma_channel_np); + + return 0; +} +EXPORT_SYMBOL(fsl_asoc_get_dma_channel); + +MODULE_AUTHOR("Timur Tabi <timur@freescale.com>"); +MODULE_DESCRIPTION("Freescale ASoC utility code"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/fsl/fsl_utils.h b/sound/soc/fsl/fsl_utils.h new file mode 100644 index 00000000000..b2951126527 --- /dev/null +++ b/sound/soc/fsl/fsl_utils.h @@ -0,0 +1,26 @@ +/** + * Freescale ALSA SoC Machine driver utility + * + * Author: Timur Tabi <timur@freescale.com> + * + * Copyright 2010 Freescale Semiconductor, Inc. + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ + +#ifndef _FSL_UTILS_H +#define _FSL_UTILS_H + +#define DAI_NAME_SIZE 32 + +struct snd_soc_dai_link; +struct device_node; + +int fsl_asoc_get_dma_channel(struct device_node *ssi_np, const char *name, + struct snd_soc_dai_link *dai, + unsigned int *dma_channel_id, + unsigned int *dma_id); + +#endif /* _FSL_UTILS_H */ diff --git a/sound/soc/imx/imx-audmux.c b/sound/soc/fsl/imx-audmux.c index f23700359c6..f23700359c6 100644 --- a/sound/soc/imx/imx-audmux.c +++ b/sound/soc/fsl/imx-audmux.c diff --git a/sound/soc/imx/imx-audmux.h b/sound/soc/fsl/imx-audmux.h index 04ebbab8d7b..04ebbab8d7b 100644 --- a/sound/soc/imx/imx-audmux.h +++ b/sound/soc/fsl/imx-audmux.h diff --git a/sound/soc/fsl/imx-mc13783.c b/sound/soc/fsl/imx-mc13783.c new file mode 100644 index 00000000000..f59c3494366 --- /dev/null +++ b/sound/soc/fsl/imx-mc13783.c @@ -0,0 +1,156 @@ +/* + * imx-mc13783.c -- SoC audio for imx based boards with mc13783 codec + * + * Copyright 2012 Philippe Retornaz, <philippe.retornaz@epfl.ch> + * + * Heavly based on phycore-mc13783: + * Copyright 2009 Sascha Hauer, Pengutronix <s.hauer@pengutronix.de> + * + * 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/device.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <asm/mach-types.h> + +#include "../codecs/mc13783.h" +#include "imx-ssi.h" +#include "imx-audmux.h" + +#define FMT_SSI (SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_NB_NF | \ + SND_SOC_DAIFMT_CBM_CFM) + +static int imx_mc13783_hifi_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 ret; + + ret = snd_soc_dai_set_tdm_slot(codec_dai, 0xfffffffc, 0xfffffffc, + 4, 16); + if (ret) + return ret; + + ret = snd_soc_dai_set_sysclk(codec_dai, MC13783_CLK_CLIA, 26000000, 0); + if (ret) + return ret; + + ret = snd_soc_dai_set_tdm_slot(cpu_dai, 0x0, 0xfffffffc, 2, 16); + if (ret) + return ret; + + return 0; +} + +static struct snd_soc_ops imx_mc13783_hifi_ops = { + .hw_params = imx_mc13783_hifi_hw_params, +}; + +static struct snd_soc_dai_link imx_mc13783_dai_mc13783[] = { + { + .name = "MC13783", + .stream_name = "Sound", + .codec_dai_name = "mc13783-hifi", + .codec_name = "mc13783-codec", + .cpu_dai_name = "imx-ssi.0", + .platform_name = "imx-pcm-audio.0", + .ops = &imx_mc13783_hifi_ops, + .symmetric_rates = 1, + .dai_fmt = FMT_SSI, + }, +}; + +static const struct snd_soc_dapm_widget imx_mc13783_widget[] = { + SND_SOC_DAPM_MIC("Mic", NULL), + SND_SOC_DAPM_HP("Headphone", NULL), + SND_SOC_DAPM_SPK("Speaker", NULL), +}; + +static const struct snd_soc_dapm_route imx_mc13783_routes[] = { + {"Speaker", NULL, "LSP"}, + {"Headphone", NULL, "HSL"}, + {"Headphone", NULL, "HSR"}, + + {"MC1LIN", NULL, "MC1 Bias"}, + {"MC2IN", NULL, "MC2 Bias"}, + {"MC1 Bias", NULL, "Mic"}, + {"MC2 Bias", NULL, "Mic"}, +}; + +static struct snd_soc_card imx_mc13783 = { + .name = "imx_mc13783", + .dai_link = imx_mc13783_dai_mc13783, + .num_links = ARRAY_SIZE(imx_mc13783_dai_mc13783), + .dapm_widgets = imx_mc13783_widget, + .num_dapm_widgets = ARRAY_SIZE(imx_mc13783_widget), + .dapm_routes = imx_mc13783_routes, + .num_dapm_routes = ARRAY_SIZE(imx_mc13783_routes), +}; + +static int __devinit imx_mc13783_probe(struct platform_device *pdev) +{ + int ret; + + imx_mc13783.dev = &pdev->dev; + + ret = snd_soc_register_card(&imx_mc13783); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", + ret); + return ret; + } + + imx_audmux_v2_configure_port(MX31_AUDMUX_PORT4_SSI_PINS_4, + IMX_AUDMUX_V2_PTCR_SYN, + IMX_AUDMUX_V2_PDCR_RXDSEL(MX31_AUDMUX_PORT1_SSI0) | + IMX_AUDMUX_V2_PDCR_MODE(1) | + IMX_AUDMUX_V2_PDCR_INMMASK(0xfc)); + imx_audmux_v2_configure_port(MX31_AUDMUX_PORT1_SSI0, + IMX_AUDMUX_V2_PTCR_SYN | + IMX_AUDMUX_V2_PTCR_TFSDIR | + IMX_AUDMUX_V2_PTCR_TFSEL(MX31_AUDMUX_PORT4_SSI_PINS_4) | + IMX_AUDMUX_V2_PTCR_TCLKDIR | + IMX_AUDMUX_V2_PTCR_TCSEL(MX31_AUDMUX_PORT4_SSI_PINS_4) | + IMX_AUDMUX_V2_PTCR_RFSDIR | + IMX_AUDMUX_V2_PTCR_RFSEL(MX31_AUDMUX_PORT4_SSI_PINS_4) | + IMX_AUDMUX_V2_PTCR_RCLKDIR | + IMX_AUDMUX_V2_PTCR_RCSEL(MX31_AUDMUX_PORT4_SSI_PINS_4), + IMX_AUDMUX_V2_PDCR_RXDSEL(MX31_AUDMUX_PORT4_SSI_PINS_4)); + + return ret; +} + +static int __devexit imx_mc13783_remove(struct platform_device *pdev) +{ + snd_soc_unregister_card(&imx_mc13783); + + return 0; +} + +static struct platform_driver imx_mc13783_audio_driver = { + .driver = { + .name = "imx_mc13783", + .owner = THIS_MODULE, + }, + .probe = imx_mc13783_probe, + .remove = __devexit_p(imx_mc13783_remove) +}; + +module_platform_driver(imx_mc13783_audio_driver); + +MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>"); +MODULE_AUTHOR("Philippe Retornaz <philippe.retornaz@epfl.ch"); +MODULE_DESCRIPTION("imx with mc13783 codec ALSA SoC driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:imx_mc13783"); diff --git a/sound/soc/imx/imx-pcm-dma-mx2.c b/sound/soc/fsl/imx-pcm-dma.c index 6b818de2fc0..f3c0a5ef35c 100644 --- a/sound/soc/imx/imx-pcm-dma-mx2.c +++ b/sound/soc/fsl/imx-pcm-dma.c @@ -109,7 +109,8 @@ static int snd_imx_open(struct snd_pcm_substream *substream) dma_params = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); dma_data = kzalloc(sizeof(*dma_data), GFP_KERNEL); - dma_data->peripheral_type = IMX_DMATYPE_SSI; + dma_data->peripheral_type = dma_params->shared_peripheral ? + IMX_DMATYPE_SSI_SP : IMX_DMATYPE_SSI; dma_data->priority = DMA_PRIO_HIGH; dma_data->dma_request = dma_params->dma; diff --git a/sound/soc/imx/imx-pcm-fiq.c b/sound/soc/fsl/imx-pcm-fiq.c index 456b7d723d6..456b7d723d6 100644 --- a/sound/soc/imx/imx-pcm-fiq.c +++ b/sound/soc/fsl/imx-pcm-fiq.c diff --git a/sound/soc/imx/imx-pcm.c b/sound/soc/fsl/imx-pcm.c index 93dc360b177..93dc360b177 100644 --- a/sound/soc/imx/imx-pcm.c +++ b/sound/soc/fsl/imx-pcm.c diff --git a/sound/soc/imx/imx-pcm.h b/sound/soc/fsl/imx-pcm.h index b5f5c3acf34..83c0ed7d55c 100644 --- a/sound/soc/imx/imx-pcm.h +++ b/sound/soc/fsl/imx-pcm.h @@ -22,6 +22,7 @@ struct imx_pcm_dma_params { int dma; unsigned long dma_addr; int burstsize; + bool shared_peripheral; /* The peripheral is on SPBA bus */ }; int snd_imx_pcm_mmap(struct snd_pcm_substream *substream, diff --git a/sound/soc/fsl/imx-sgtl5000.c b/sound/soc/fsl/imx-sgtl5000.c new file mode 100644 index 00000000000..3a729caeb8c --- /dev/null +++ b/sound/soc/fsl/imx-sgtl5000.c @@ -0,0 +1,221 @@ +/* + * Copyright 2012 Freescale Semiconductor, Inc. + * Copyright 2012 Linaro Ltd. + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/of_i2c.h> +#include <linux/clk.h> +#include <sound/soc.h> + +#include "../codecs/sgtl5000.h" +#include "imx-audmux.h" + +#define DAI_NAME_SIZE 32 + +struct imx_sgtl5000_data { + struct snd_soc_dai_link dai; + struct snd_soc_card card; + char codec_dai_name[DAI_NAME_SIZE]; + char platform_name[DAI_NAME_SIZE]; + struct clk *codec_clk; + unsigned int clk_frequency; +}; + +static int imx_sgtl5000_dai_init(struct snd_soc_pcm_runtime *rtd) +{ + struct imx_sgtl5000_data *data = container_of(rtd->card, + struct imx_sgtl5000_data, card); + struct device *dev = rtd->card->dev; + int ret; + + ret = snd_soc_dai_set_sysclk(rtd->codec_dai, SGTL5000_SYSCLK, + data->clk_frequency, SND_SOC_CLOCK_IN); + if (ret) { + dev_err(dev, "could not set codec driver clock params\n"); + return ret; + } + + return 0; +} + +static const struct snd_soc_dapm_widget imx_sgtl5000_dapm_widgets[] = { + SND_SOC_DAPM_MIC("Mic Jack", NULL), + SND_SOC_DAPM_LINE("Line In Jack", NULL), + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_SPK("Line Out Jack", NULL), + SND_SOC_DAPM_SPK("Ext Spk", NULL), +}; + +static int __devinit imx_sgtl5000_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct device_node *ssi_np, *codec_np; + struct platform_device *ssi_pdev; + struct i2c_client *codec_dev; + struct imx_sgtl5000_data *data; + int int_port, ext_port; + int ret; + + ret = of_property_read_u32(np, "mux-int-port", &int_port); + if (ret) { + dev_err(&pdev->dev, "mux-int-port missing or invalid\n"); + return ret; + } + ret = of_property_read_u32(np, "mux-ext-port", &ext_port); + if (ret) { + dev_err(&pdev->dev, "mux-ext-port missing or invalid\n"); + return ret; + } + + /* + * The port numbering in the hardware manual starts at 1, while + * the audmux API expects it starts at 0. + */ + int_port--; + ext_port--; + ret = imx_audmux_v2_configure_port(int_port, + IMX_AUDMUX_V2_PTCR_SYN | + IMX_AUDMUX_V2_PTCR_TFSEL(ext_port) | + IMX_AUDMUX_V2_PTCR_TCSEL(ext_port) | + IMX_AUDMUX_V2_PTCR_TFSDIR | + IMX_AUDMUX_V2_PTCR_TCLKDIR, + IMX_AUDMUX_V2_PDCR_RXDSEL(ext_port)); + if (ret) { + dev_err(&pdev->dev, "audmux internal port setup failed\n"); + return ret; + } + imx_audmux_v2_configure_port(ext_port, + IMX_AUDMUX_V2_PTCR_SYN | + IMX_AUDMUX_V2_PTCR_TCSEL(int_port), + IMX_AUDMUX_V2_PDCR_RXDSEL(int_port)); + if (ret) { + dev_err(&pdev->dev, "audmux external port setup failed\n"); + return ret; + } + + ssi_np = of_parse_phandle(pdev->dev.of_node, "ssi-controller", 0); + codec_np = of_parse_phandle(pdev->dev.of_node, "audio-codec", 0); + if (!ssi_np || !codec_np) { + dev_err(&pdev->dev, "phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + ssi_pdev = of_find_device_by_node(ssi_np); + if (!ssi_pdev) { + dev_err(&pdev->dev, "failed to find SSI platform device\n"); + ret = -EINVAL; + goto fail; + } + codec_dev = of_find_i2c_device_by_node(codec_np); + if (!codec_dev) { + dev_err(&pdev->dev, "failed to find codec platform device\n"); + return -EINVAL; + } + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) { + ret = -ENOMEM; + goto fail; + } + + data->codec_clk = clk_get(&codec_dev->dev, NULL); + if (IS_ERR(data->codec_clk)) { + /* assuming clock enabled by default */ + data->codec_clk = NULL; + ret = of_property_read_u32(codec_np, "clock-frequency", + &data->clk_frequency); + if (ret) { + dev_err(&codec_dev->dev, + "clock-frequency missing or invalid\n"); + goto fail; + } + } else { + data->clk_frequency = clk_get_rate(data->codec_clk); + clk_prepare_enable(data->codec_clk); + } + + data->dai.name = "HiFi"; + data->dai.stream_name = "HiFi"; + data->dai.codec_dai_name = "sgtl5000"; + data->dai.codec_of_node = codec_np; + data->dai.cpu_dai_name = dev_name(&ssi_pdev->dev); + data->dai.platform_name = "imx-pcm-audio"; + data->dai.init = &imx_sgtl5000_dai_init; + data->dai.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM; + + data->card.dev = &pdev->dev; + ret = snd_soc_of_parse_card_name(&data->card, "model"); + if (ret) + goto clk_fail; + ret = snd_soc_of_parse_audio_routing(&data->card, "audio-routing"); + if (ret) + goto clk_fail; + data->card.num_links = 1; + data->card.dai_link = &data->dai; + data->card.dapm_widgets = imx_sgtl5000_dapm_widgets; + data->card.num_dapm_widgets = ARRAY_SIZE(imx_sgtl5000_dapm_widgets); + + ret = snd_soc_register_card(&data->card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret); + goto clk_fail; + } + + platform_set_drvdata(pdev, data); +clk_fail: + clk_put(data->codec_clk); +fail: + if (ssi_np) + of_node_put(ssi_np); + if (codec_np) + of_node_put(codec_np); + + return ret; +} + +static int __devexit imx_sgtl5000_remove(struct platform_device *pdev) +{ + struct imx_sgtl5000_data *data = platform_get_drvdata(pdev); + + if (data->codec_clk) { + clk_disable_unprepare(data->codec_clk); + clk_put(data->codec_clk); + } + snd_soc_unregister_card(&data->card); + + return 0; +} + +static const struct of_device_id imx_sgtl5000_dt_ids[] = { + { .compatible = "fsl,imx-audio-sgtl5000", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx_sgtl5000_dt_ids); + +static struct platform_driver imx_sgtl5000_driver = { + .driver = { + .name = "imx-sgtl5000", + .owner = THIS_MODULE, + .of_match_table = imx_sgtl5000_dt_ids, + }, + .probe = imx_sgtl5000_probe, + .remove = __devexit_p(imx_sgtl5000_remove), +}; +module_platform_driver(imx_sgtl5000_driver); + +MODULE_AUTHOR("Shawn Guo <shawn.guo@linaro.org>"); +MODULE_DESCRIPTION("Freescale i.MX SGTL5000 ASoC machine driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:imx-sgtl5000"); diff --git a/sound/soc/imx/imx-ssi.c b/sound/soc/fsl/imx-ssi.c index 4f81ed45632..cf3ed0362c9 100644 --- a/sound/soc/imx/imx-ssi.c +++ b/sound/soc/fsl/imx-ssi.c @@ -28,7 +28,7 @@ * value. When we read the same register two times (and the register still * contains the same value) these status bits are not set. We work * around this by not polling these bits but only wait a fixed delay. - * + * */ #include <linux/clk.h> diff --git a/sound/soc/imx/imx-ssi.h b/sound/soc/fsl/imx-ssi.h index 5744e86ca87..5744e86ca87 100644 --- a/sound/soc/imx/imx-ssi.h +++ b/sound/soc/fsl/imx-ssi.h diff --git a/sound/soc/fsl/mpc8610_hpcd.c b/sound/soc/fsl/mpc8610_hpcd.c index 3fea5a15ffe..60bcba1bc30 100644 --- a/sound/soc/fsl/mpc8610_hpcd.c +++ b/sound/soc/fsl/mpc8610_hpcd.c @@ -14,18 +14,16 @@ #include <linux/interrupt.h> #include <linux/of_device.h> #include <linux/slab.h> -#include <linux/of_i2c.h> #include <sound/soc.h> #include <asm/fsl_guts.h> #include "fsl_dma.h" #include "fsl_ssi.h" +#include "fsl_utils.h" /* There's only one global utilities register */ static phys_addr_t guts_phys; -#define DAI_NAME_SIZE 32 - /** * mpc8610_hpcd_data: machine-specific ASoC device data * @@ -43,7 +41,6 @@ struct mpc8610_hpcd_data { unsigned int dma_id[2]; /* 0 = DMA1, 1 = DMA2, etc */ unsigned int dma_channel_id[2]; /* 0 = ch 0, 1 = ch 1, etc*/ char codec_dai_name[DAI_NAME_SIZE]; - char codec_name[DAI_NAME_SIZE]; char platform_name[2][DAI_NAME_SIZE]; /* One for each DMA channel */ }; @@ -181,141 +178,6 @@ static struct snd_soc_ops mpc8610_hpcd_ops = { }; /** - * get_node_by_phandle_name - get a node by its phandle name - * - * This function takes a node, the name of a property in that node, and a - * compatible string. Assuming the property is a phandle to another node, - * it returns that node, (optionally) if that node is compatible. - * - * If the property is not a phandle, or the node it points to is not compatible - * with the specific string, then NULL is returned. - */ -static struct device_node *get_node_by_phandle_name(struct device_node *np, - const char *name, - const char *compatible) -{ - const phandle *ph; - int len; - - ph = of_get_property(np, name, &len); - if (!ph || (len != sizeof(phandle))) - return NULL; - - np = of_find_node_by_phandle(*ph); - if (!np) - return NULL; - - if (compatible && !of_device_is_compatible(np, compatible)) { - of_node_put(np); - return NULL; - } - - return np; -} - -/** - * get_parent_cell_index -- return the cell-index of the parent of a node - * - * Return the value of the cell-index property of the parent of the given - * node. This is used for DMA channel nodes that need to know the DMA ID - * of the controller they are on. - */ -static int get_parent_cell_index(struct device_node *np) -{ - struct device_node *parent = of_get_parent(np); - const u32 *iprop; - - if (!parent) - return -1; - - iprop = of_get_property(parent, "cell-index", NULL); - of_node_put(parent); - - if (!iprop) - return -1; - - return be32_to_cpup(iprop); -} - -/** - * codec_node_dev_name - determine the dev_name for a codec node - * - * This function determines the dev_name for an I2C node. This is the name - * that would be returned by dev_name() if this device_node were part of a - * 'struct device' It's ugly and hackish, but it works. - * - * The dev_name for such devices include the bus number and I2C address. For - * example, "cs4270.0-004f". - */ -static int codec_node_dev_name(struct device_node *np, char *buf, size_t len) -{ - const u32 *iprop; - int addr; - char temp[DAI_NAME_SIZE]; - struct i2c_client *i2c; - - of_modalias_node(np, temp, DAI_NAME_SIZE); - - iprop = of_get_property(np, "reg", NULL); - if (!iprop) - return -EINVAL; - - addr = be32_to_cpup(iprop); - - /* We need the adapter number */ - i2c = of_find_i2c_device_by_node(np); - if (!i2c) - return -ENODEV; - - snprintf(buf, len, "%s.%u-%04x", temp, i2c->adapter->nr, addr); - - return 0; -} - -static int get_dma_channel(struct device_node *ssi_np, - const char *name, - struct snd_soc_dai_link *dai, - unsigned int *dma_channel_id, - unsigned int *dma_id) -{ - struct resource res; - struct device_node *dma_channel_np; - const u32 *iprop; - int ret; - - dma_channel_np = get_node_by_phandle_name(ssi_np, name, - "fsl,ssi-dma-channel"); - if (!dma_channel_np) - return -EINVAL; - - /* Determine the dev_name for the device_node. This code mimics the - * behavior of of_device_make_bus_id(). We need this because ASoC uses - * the dev_name() of the device to match the platform (DMA) device with - * the CPU (SSI) device. It's all ugly and hackish, but it works (for - * now). - * - * dai->platform name should already point to an allocated buffer. - */ - ret = of_address_to_resource(dma_channel_np, 0, &res); - if (ret) - return ret; - snprintf((char *)dai->platform_name, DAI_NAME_SIZE, "%llx.%s", - (unsigned long long) res.start, dma_channel_np->name); - - iprop = of_get_property(dma_channel_np, "cell-index", NULL); - if (!iprop) { - of_node_put(dma_channel_np); - return -EINVAL; - } - - *dma_channel_id = be32_to_cpup(iprop); - *dma_id = get_parent_cell_index(dma_channel_np); - of_node_put(dma_channel_np); - - return 0; -} - -/** * mpc8610_hpcd_probe: platform probe function for the machine driver * * Although this is a machine driver, the SSI node is the "master" node with @@ -352,16 +214,8 @@ static int mpc8610_hpcd_probe(struct platform_device *pdev) machine_data->dai[0].cpu_dai_name = dev_name(&ssi_pdev->dev); machine_data->dai[0].ops = &mpc8610_hpcd_ops; - /* Determine the codec name, it will be used as the codec DAI name */ - ret = codec_node_dev_name(codec_np, machine_data->codec_name, - DAI_NAME_SIZE); - if (ret) { - dev_err(&pdev->dev, "invalid codec node %s\n", - codec_np->full_name); - ret = -EINVAL; - goto error; - } - machine_data->dai[0].codec_name = machine_data->codec_name; + /* ASoC core can match codec with device node */ + machine_data->dai[0].codec_of_node = codec_np; /* The DAI name from the codec (snd_soc_dai_driver.name) */ machine_data->dai[0].codec_dai_name = "cs4270-hifi"; @@ -458,9 +312,10 @@ static int mpc8610_hpcd_probe(struct platform_device *pdev) /* Find the playback DMA channel to use. */ machine_data->dai[0].platform_name = machine_data->platform_name[0]; - ret = get_dma_channel(np, "fsl,playback-dma", &machine_data->dai[0], - &machine_data->dma_channel_id[0], - &machine_data->dma_id[0]); + ret = fsl_asoc_get_dma_channel(np, "fsl,playback-dma", + &machine_data->dai[0], + &machine_data->dma_channel_id[0], + &machine_data->dma_id[0]); if (ret) { dev_err(&pdev->dev, "missing/invalid playback DMA phandle\n"); goto error; @@ -468,9 +323,10 @@ static int mpc8610_hpcd_probe(struct platform_device *pdev) /* Find the capture DMA channel to use. */ machine_data->dai[1].platform_name = machine_data->platform_name[1]; - ret = get_dma_channel(np, "fsl,capture-dma", &machine_data->dai[1], - &machine_data->dma_channel_id[1], - &machine_data->dma_id[1]); + ret = fsl_asoc_get_dma_channel(np, "fsl,capture-dma", + &machine_data->dai[1], + &machine_data->dma_channel_id[1], + &machine_data->dma_id[1]); if (ret) { dev_err(&pdev->dev, "missing/invalid capture DMA phandle\n"); goto error; diff --git a/sound/soc/imx/mx27vis-aic32x4.c b/sound/soc/fsl/mx27vis-aic32x4.c index f6d04ad4bb3..f6d04ad4bb3 100644 --- a/sound/soc/imx/mx27vis-aic32x4.c +++ b/sound/soc/fsl/mx27vis-aic32x4.c diff --git a/sound/soc/fsl/p1022_ds.c b/sound/soc/fsl/p1022_ds.c index 982a1c94498..50adf4032bc 100644 --- a/sound/soc/fsl/p1022_ds.c +++ b/sound/soc/fsl/p1022_ds.c @@ -14,12 +14,12 @@ #include <linux/interrupt.h> #include <linux/of_device.h> #include <linux/slab.h> -#include <linux/of_i2c.h> #include <sound/soc.h> #include <asm/fsl_guts.h> #include "fsl_dma.h" #include "fsl_ssi.h" +#include "fsl_utils.h" /* P1022-specific PMUXCR and DMUXCR bit definitions */ @@ -57,8 +57,6 @@ static inline void guts_set_dmuxcr(struct ccsr_guts __iomem *guts, /* There's only one global utilities register */ static phys_addr_t guts_phys; -#define DAI_NAME_SIZE 32 - /** * machine_data: machine-specific ASoC device data * @@ -75,7 +73,6 @@ struct machine_data { unsigned int ssi_id; /* 0 = SSI1, 1 = SSI2, etc */ unsigned int dma_id[2]; /* 0 = DMA1, 1 = DMA2, etc */ unsigned int dma_channel_id[2]; /* 0 = ch 0, 1 = ch 1, etc*/ - char codec_name[DAI_NAME_SIZE]; char platform_name[2][DAI_NAME_SIZE]; /* One for each DMA channel */ }; @@ -191,136 +188,6 @@ static struct snd_soc_ops p1022_ds_ops = { }; /** - * get_node_by_phandle_name - get a node by its phandle name - * - * This function takes a node, the name of a property in that node, and a - * compatible string. Assuming the property is a phandle to another node, - * it returns that node, (optionally) if that node is compatible. - * - * If the property is not a phandle, or the node it points to is not compatible - * with the specific string, then NULL is returned. - */ -static struct device_node *get_node_by_phandle_name(struct device_node *np, - const char *name, const char *compatible) -{ - np = of_parse_phandle(np, name, 0); - if (!np) - return NULL; - - if (!of_device_is_compatible(np, compatible)) { - of_node_put(np); - return NULL; - } - - return np; -} - -/** - * get_parent_cell_index -- return the cell-index of the parent of a node - * - * Return the value of the cell-index property of the parent of the given - * node. This is used for DMA channel nodes that need to know the DMA ID - * of the controller they are on. - */ -static int get_parent_cell_index(struct device_node *np) -{ - struct device_node *parent = of_get_parent(np); - const u32 *iprop; - int ret = -1; - - if (!parent) - return -1; - - iprop = of_get_property(parent, "cell-index", NULL); - if (iprop) - ret = be32_to_cpup(iprop); - - of_node_put(parent); - - return ret; -} - -/** - * codec_node_dev_name - determine the dev_name for a codec node - * - * This function determines the dev_name for an I2C node. This is the name - * that would be returned by dev_name() if this device_node were part of a - * 'struct device' It's ugly and hackish, but it works. - * - * The dev_name for such devices include the bus number and I2C address. For - * example, "cs4270-codec.0-004f". - */ -static int codec_node_dev_name(struct device_node *np, char *buf, size_t len) -{ - const u32 *iprop; - int addr; - char temp[DAI_NAME_SIZE]; - struct i2c_client *i2c; - - of_modalias_node(np, temp, DAI_NAME_SIZE); - - iprop = of_get_property(np, "reg", NULL); - if (!iprop) - return -EINVAL; - - addr = be32_to_cpup(iprop); - - /* We need the adapter number */ - i2c = of_find_i2c_device_by_node(np); - if (!i2c) - return -ENODEV; - - snprintf(buf, len, "%s.%u-%04x", temp, i2c->adapter->nr, addr); - - return 0; -} - -static int get_dma_channel(struct device_node *ssi_np, - const char *name, - struct snd_soc_dai_link *dai, - unsigned int *dma_channel_id, - unsigned int *dma_id) -{ - struct resource res; - struct device_node *dma_channel_np; - const u32 *iprop; - int ret; - - dma_channel_np = get_node_by_phandle_name(ssi_np, name, - "fsl,ssi-dma-channel"); - if (!dma_channel_np) - return -EINVAL; - - /* Determine the dev_name for the device_node. This code mimics the - * behavior of of_device_make_bus_id(). We need this because ASoC uses - * the dev_name() of the device to match the platform (DMA) device with - * the CPU (SSI) device. It's all ugly and hackish, but it works (for - * now). - * - * dai->platform name should already point to an allocated buffer. - */ - ret = of_address_to_resource(dma_channel_np, 0, &res); - if (ret) { - of_node_put(dma_channel_np); - return ret; - } - snprintf((char *)dai->platform_name, DAI_NAME_SIZE, "%llx.%s", - (unsigned long long) res.start, dma_channel_np->name); - - iprop = of_get_property(dma_channel_np, "cell-index", NULL); - if (!iprop) { - of_node_put(dma_channel_np); - return -EINVAL; - } - - *dma_channel_id = be32_to_cpup(iprop); - *dma_id = get_parent_cell_index(dma_channel_np); - of_node_put(dma_channel_np); - - return 0; -} - -/** * p1022_ds_probe: platform probe function for the machine driver * * Although this is a machine driver, the SSI node is the "master" node with @@ -357,15 +224,8 @@ static int p1022_ds_probe(struct platform_device *pdev) mdata->dai[0].cpu_dai_name = dev_name(&ssi_pdev->dev); mdata->dai[0].ops = &p1022_ds_ops; - /* Determine the codec name, it will be used as the codec DAI name */ - ret = codec_node_dev_name(codec_np, mdata->codec_name, DAI_NAME_SIZE); - if (ret) { - dev_err(&pdev->dev, "invalid codec node %s\n", - codec_np->full_name); - ret = -EINVAL; - goto error; - } - mdata->dai[0].codec_name = mdata->codec_name; + /* ASoC core can match codec with device node */ + mdata->dai[0].codec_of_node = codec_np; /* We register two DAIs per SSI, one for playback and the other for * capture. We support codecs that have separate DAIs for both playback @@ -462,9 +322,9 @@ static int p1022_ds_probe(struct platform_device *pdev) /* Find the playback DMA channel to use. */ mdata->dai[0].platform_name = mdata->platform_name[0]; - ret = get_dma_channel(np, "fsl,playback-dma", &mdata->dai[0], - &mdata->dma_channel_id[0], - &mdata->dma_id[0]); + ret = fsl_asoc_get_dma_channel(np, "fsl,playback-dma", &mdata->dai[0], + &mdata->dma_channel_id[0], + &mdata->dma_id[0]); if (ret) { dev_err(&pdev->dev, "missing/invalid playback DMA phandle\n"); goto error; @@ -472,9 +332,9 @@ static int p1022_ds_probe(struct platform_device *pdev) /* Find the capture DMA channel to use. */ mdata->dai[1].platform_name = mdata->platform_name[1]; - ret = get_dma_channel(np, "fsl,capture-dma", &mdata->dai[1], - &mdata->dma_channel_id[1], - &mdata->dma_id[1]); + ret = fsl_asoc_get_dma_channel(np, "fsl,capture-dma", &mdata->dai[1], + &mdata->dma_channel_id[1], + &mdata->dma_id[1]); if (ret) { dev_err(&pdev->dev, "missing/invalid capture DMA phandle\n"); goto error; diff --git a/sound/soc/imx/phycore-ac97.c b/sound/soc/fsl/phycore-ac97.c index f8da6dd115e..f8da6dd115e 100644 --- a/sound/soc/imx/phycore-ac97.c +++ b/sound/soc/fsl/phycore-ac97.c diff --git a/sound/soc/imx/wm1133-ev1.c b/sound/soc/fsl/wm1133-ev1.c index fe54a69073e..fe54a69073e 100644 --- a/sound/soc/imx/wm1133-ev1.c +++ b/sound/soc/fsl/wm1133-ev1.c diff --git a/sound/soc/generic/Kconfig b/sound/soc/generic/Kconfig new file mode 100644 index 00000000000..610f6125164 --- /dev/null +++ b/sound/soc/generic/Kconfig @@ -0,0 +1,4 @@ +config SND_SIMPLE_CARD + tristate "ASoC Simple sound card support" + help + This option enables generic simple sound card support diff --git a/sound/soc/generic/Makefile b/sound/soc/generic/Makefile new file mode 100644 index 00000000000..9c3b246792b --- /dev/null +++ b/sound/soc/generic/Makefile @@ -0,0 +1,3 @@ +snd-soc-simple-card-objs := simple-card.o + +obj-$(CONFIG_SND_SIMPLE_CARD) += snd-soc-simple-card.o diff --git a/sound/soc/generic/simple-card.c b/sound/soc/generic/simple-card.c new file mode 100644 index 00000000000..b4b4cab3023 --- /dev/null +++ b/sound/soc/generic/simple-card.c @@ -0,0 +1,114 @@ +/* + * ASoC simple sound card support + * + * Copyright (C) 2012 Renesas Solutions Corp. + * Kuninori Morimoto <kuninori.morimoto.gx@renesas.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/platform_device.h> +#include <linux/module.h> +#include <sound/simple_card.h> + +#define asoc_simple_get_card_info(p) \ + container_of(p->dai_link, struct asoc_simple_card_info, snd_link) + +static int asoc_simple_card_dai_init(struct snd_soc_pcm_runtime *rtd) +{ + struct asoc_simple_card_info *cinfo = asoc_simple_get_card_info(rtd); + struct asoc_simple_dai_init_info *iinfo = cinfo->init; + struct snd_soc_dai *codec = rtd->codec_dai; + struct snd_soc_dai *cpu = rtd->cpu_dai; + unsigned int cpu_daifmt = iinfo->fmt | iinfo->cpu_daifmt; + unsigned int codec_daifmt = iinfo->fmt | iinfo->codec_daifmt; + int ret; + + if (codec_daifmt) { + ret = snd_soc_dai_set_fmt(codec, codec_daifmt); + if (ret < 0) + return ret; + } + + if (iinfo->sysclk) { + ret = snd_soc_dai_set_sysclk(codec, 0, iinfo->sysclk, 0); + if (ret < 0) + return ret; + } + + if (cpu_daifmt) { + ret = snd_soc_dai_set_fmt(cpu, cpu_daifmt); + if (ret < 0) + return ret; + } + + return 0; +} + +static int asoc_simple_card_probe(struct platform_device *pdev) +{ + struct asoc_simple_card_info *cinfo = pdev->dev.platform_data; + + if (!cinfo) { + dev_err(&pdev->dev, "no info for asoc-simple-card\n"); + return -EINVAL; + } + + if (!cinfo->name || + !cinfo->card || + !cinfo->cpu_dai || + !cinfo->codec || + !cinfo->platform || + !cinfo->codec_dai) { + dev_err(&pdev->dev, "insufficient asoc_simple_card_info settings\n"); + return -EINVAL; + } + + /* + * init snd_soc_dai_link + */ + cinfo->snd_link.name = cinfo->name; + cinfo->snd_link.stream_name = cinfo->name; + cinfo->snd_link.cpu_dai_name = cinfo->cpu_dai; + cinfo->snd_link.platform_name = cinfo->platform; + cinfo->snd_link.codec_name = cinfo->codec; + cinfo->snd_link.codec_dai_name = cinfo->codec_dai; + + /* enable snd_link.init if cinfo has settings */ + if (cinfo->init) + cinfo->snd_link.init = asoc_simple_card_dai_init; + + /* + * init snd_soc_card + */ + cinfo->snd_card.name = cinfo->card; + cinfo->snd_card.owner = THIS_MODULE; + cinfo->snd_card.dai_link = &cinfo->snd_link; + cinfo->snd_card.num_links = 1; + cinfo->snd_card.dev = &pdev->dev; + + return snd_soc_register_card(&cinfo->snd_card); +} + +static int asoc_simple_card_remove(struct platform_device *pdev) +{ + struct asoc_simple_card_info *cinfo = pdev->dev.platform_data; + + return snd_soc_unregister_card(&cinfo->snd_card); +} + +static struct platform_driver asoc_simple_card = { + .driver = { + .name = "asoc-simple-card", + }, + .probe = asoc_simple_card_probe, + .remove = asoc_simple_card_remove, +}; + +module_platform_driver(asoc_simple_card); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("ASoC Simple Sound Card"); +MODULE_AUTHOR("Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>"); diff --git a/sound/soc/imx/Kconfig b/sound/soc/imx/Kconfig deleted file mode 100644 index d83e5d0b5d5..00000000000 --- a/sound/soc/imx/Kconfig +++ /dev/null @@ -1,79 +0,0 @@ -menuconfig SND_IMX_SOC - tristate "SoC Audio for Freescale i.MX CPUs" - depends on ARCH_MXC - help - Say Y or M if you want to add support for codecs attached to - the i.MX SSI interface. - - -if SND_IMX_SOC - -config SND_SOC_IMX_SSI - tristate - -config SND_SOC_IMX_PCM - tristate - -config SND_MXC_SOC_FIQ - tristate - select FIQ - select SND_SOC_IMX_PCM - -config SND_MXC_SOC_MX2 - select SND_SOC_DMAENGINE_PCM - tristate - select SND_SOC_IMX_PCM - -config SND_SOC_IMX_AUDMUX - tristate - -config SND_MXC_SOC_WM1133_EV1 - tristate "Audio on the i.MX31ADS with WM1133-EV1 fitted" - depends on MACH_MX31ADS_WM1133_EV1 && EXPERIMENTAL - select SND_SOC_WM8350 - select SND_MXC_SOC_FIQ - select SND_SOC_IMX_AUDMUX - select SND_SOC_IMX_SSI - help - Enable support for audio on the i.MX31ADS with the WM1133-EV1 - PMIC board with WM8835x fitted. - -config SND_SOC_MX27VIS_AIC32X4 - tristate "SoC audio support for Visstrim M10 boards" - depends on MACH_IMX27_VISSTRIM_M10 && I2C - select SND_SOC_TLV320AIC32X4 - select SND_MXC_SOC_MX2 - select SND_SOC_IMX_AUDMUX - select SND_SOC_IMX_SSI - help - Say Y if you want to add support for SoC audio on Visstrim SM10 - board with TLV320AIC32X4 codec. - -config SND_SOC_PHYCORE_AC97 - tristate "SoC Audio support for Phytec phyCORE (and phyCARD) boards" - depends on MACH_PCM043 || MACH_PCA100 - select SND_SOC_AC97_BUS - select SND_SOC_WM9712 - select SND_MXC_SOC_FIQ - select SND_SOC_IMX_AUDMUX - select SND_SOC_IMX_SSI - help - Say Y if you want to add support for SoC audio on Phytec phyCORE - and phyCARD boards in AC97 mode - -config SND_SOC_EUKREA_TLV320 - tristate "Eukrea TLV320" - depends on MACH_EUKREA_MBIMX27_BASEBOARD \ - || MACH_EUKREA_MBIMXSD25_BASEBOARD \ - || MACH_EUKREA_MBIMXSD35_BASEBOARD \ - || MACH_EUKREA_MBIMXSD51_BASEBOARD - depends on I2C - select SND_SOC_TLV320AIC23 - select SND_MXC_SOC_FIQ - select SND_SOC_IMX_AUDMUX - select SND_SOC_IMX_SSI - help - Enable I2S based access to the TLV320AIC23B codec attached - to the SSI interface - -endif # SND_IMX_SOC diff --git a/sound/soc/imx/Makefile b/sound/soc/imx/Makefile deleted file mode 100644 index f5db3e92d0d..00000000000 --- a/sound/soc/imx/Makefile +++ /dev/null @@ -1,22 +0,0 @@ -# i.MX Platform Support -snd-soc-imx-ssi-objs := imx-ssi.o -snd-soc-imx-audmux-objs := imx-audmux.o - -obj-$(CONFIG_SND_SOC_IMX_SSI) += snd-soc-imx-ssi.o -obj-$(CONFIG_SND_SOC_IMX_AUDMUX) += snd-soc-imx-audmux.o - -obj-$(CONFIG_SND_SOC_IMX_PCM) += snd-soc-imx-pcm.o -snd-soc-imx-pcm-y := imx-pcm.o -snd-soc-imx-pcm-$(CONFIG_SND_MXC_SOC_FIQ) += imx-pcm-fiq.o -snd-soc-imx-pcm-$(CONFIG_SND_MXC_SOC_MX2) += imx-pcm-dma-mx2.o - -# i.MX Machine Support -snd-soc-eukrea-tlv320-objs := eukrea-tlv320.o -snd-soc-phycore-ac97-objs := phycore-ac97.o -snd-soc-mx27vis-aic32x4-objs := mx27vis-aic32x4.o -snd-soc-wm1133-ev1-objs := wm1133-ev1.o - -obj-$(CONFIG_SND_SOC_EUKREA_TLV320) += snd-soc-eukrea-tlv320.o -obj-$(CONFIG_SND_SOC_PHYCORE_AC97) += snd-soc-phycore-ac97.o -obj-$(CONFIG_SND_SOC_MX27VIS_AIC32X4) += snd-soc-mx27vis-aic32x4.o -obj-$(CONFIG_SND_MXC_SOC_WM1133_EV1) += snd-soc-wm1133-ev1.o diff --git a/sound/soc/jz4740/jz4740-i2s.c b/sound/soc/jz4740/jz4740-i2s.c index a5af7c42e62..41349670ada 100644 --- a/sound/soc/jz4740/jz4740-i2s.c +++ b/sound/soc/jz4740/jz4740-i2s.c @@ -346,7 +346,7 @@ static void jz4740_i2c_init_pcm_config(struct jz4740_i2s *i2s) /* Playback */ dma_config = &i2s->pcm_config_playback.dma_config; - dma_config->src_width = JZ4740_DMA_WIDTH_32BIT, + dma_config->src_width = JZ4740_DMA_WIDTH_32BIT; dma_config->transfer_size = JZ4740_DMA_TRANSFER_SIZE_16BYTE; dma_config->request_type = JZ4740_DMA_TYPE_AIC_TRANSMIT; dma_config->flags = JZ4740_DMA_SRC_AUTOINC; @@ -355,7 +355,7 @@ static void jz4740_i2c_init_pcm_config(struct jz4740_i2s *i2s) /* Capture */ dma_config = &i2s->pcm_config_capture.dma_config; - dma_config->dst_width = JZ4740_DMA_WIDTH_32BIT, + dma_config->dst_width = JZ4740_DMA_WIDTH_32BIT; dma_config->transfer_size = JZ4740_DMA_TRANSFER_SIZE_16BYTE; dma_config->request_type = JZ4740_DMA_TYPE_AIC_RECEIVE; dma_config->flags = JZ4740_DMA_DST_AUTOINC; diff --git a/sound/soc/mxs/mxs-pcm.c b/sound/soc/mxs/mxs-pcm.c index e373fbbc97a..373dec90579 100644 --- a/sound/soc/mxs/mxs-pcm.c +++ b/sound/soc/mxs/mxs-pcm.c @@ -220,28 +220,16 @@ static struct snd_soc_platform_driver mxs_soc_platform = { .pcm_free = mxs_pcm_free, }; -static int __devinit mxs_soc_platform_probe(struct platform_device *pdev) +int __devinit mxs_pcm_platform_register(struct device *dev) { - return snd_soc_register_platform(&pdev->dev, &mxs_soc_platform); + return snd_soc_register_platform(dev, &mxs_soc_platform); } +EXPORT_SYMBOL_GPL(mxs_pcm_platform_register); -static int __devexit mxs_soc_platform_remove(struct platform_device *pdev) +void __devexit mxs_pcm_platform_unregister(struct device *dev) { - snd_soc_unregister_platform(&pdev->dev); - - return 0; + snd_soc_unregister_platform(dev); } - -static struct platform_driver mxs_pcm_driver = { - .driver = { - .name = "mxs-pcm-audio", - .owner = THIS_MODULE, - }, - .probe = mxs_soc_platform_probe, - .remove = __devexit_p(mxs_soc_platform_remove), -}; - -module_platform_driver(mxs_pcm_driver); +EXPORT_SYMBOL_GPL(mxs_pcm_platform_unregister); MODULE_LICENSE("GPL"); -MODULE_ALIAS("platform:mxs-pcm-audio"); diff --git a/sound/soc/mxs/mxs-pcm.h b/sound/soc/mxs/mxs-pcm.h index 5f01a9124b3..35ba2ca4238 100644 --- a/sound/soc/mxs/mxs-pcm.h +++ b/sound/soc/mxs/mxs-pcm.h @@ -24,4 +24,7 @@ struct mxs_pcm_dma_params { int chan_num; }; +int mxs_pcm_platform_register(struct device *dev); +void mxs_pcm_platform_unregister(struct device *dev); + #endif diff --git a/sound/soc/mxs/mxs-saif.c b/sound/soc/mxs/mxs-saif.c index 7fd224bb732..aba71bfa33b 100644 --- a/sound/soc/mxs/mxs-saif.c +++ b/sound/soc/mxs/mxs-saif.c @@ -18,6 +18,8 @@ #include <linux/module.h> #include <linux/init.h> +#include <linux/of.h> +#include <linux/of_device.h> #include <linux/platform_device.h> #include <linux/slab.h> #include <linux/dma-mapping.h> @@ -621,37 +623,57 @@ static irqreturn_t mxs_saif_irq(int irq, void *dev_id) return IRQ_HANDLED; } -static int mxs_saif_probe(struct platform_device *pdev) +static int __devinit mxs_saif_probe(struct platform_device *pdev) { + struct device_node *np = pdev->dev.of_node; struct resource *iores, *dmares; struct mxs_saif *saif; struct mxs_saif_platform_data *pdata; struct pinctrl *pinctrl; int ret = 0; - if (pdev->id >= ARRAY_SIZE(mxs_saif)) + + if (!np && pdev->id >= ARRAY_SIZE(mxs_saif)) return -EINVAL; saif = devm_kzalloc(&pdev->dev, sizeof(*saif), GFP_KERNEL); if (!saif) return -ENOMEM; - mxs_saif[pdev->id] = saif; - saif->id = pdev->id; - - pdata = pdev->dev.platform_data; - if (pdata && !pdata->master_mode) { - saif->master_id = pdata->master_id; - if (saif->master_id < 0 || - saif->master_id >= ARRAY_SIZE(mxs_saif) || - saif->master_id == saif->id) { - dev_err(&pdev->dev, "get wrong master id\n"); - return -EINVAL; + if (np) { + struct device_node *master; + saif->id = of_alias_get_id(np, "saif"); + if (saif->id < 0) + return saif->id; + /* + * If there is no "fsl,saif-master" phandle, it's a saif + * master. Otherwise, it's a slave and its phandle points + * to the master. + */ + master = of_parse_phandle(np, "fsl,saif-master", 0); + if (!master) { + saif->master_id = saif->id; + } else { + saif->master_id = of_alias_get_id(master, "saif"); + if (saif->master_id < 0) + return saif->master_id; } } else { - saif->master_id = saif->id; + saif->id = pdev->id; + pdata = pdev->dev.platform_data; + if (pdata && !pdata->master_mode) + saif->master_id = pdata->master_id; + else + saif->master_id = saif->id; + } + + if (saif->master_id < 0 || saif->master_id >= ARRAY_SIZE(mxs_saif)) { + dev_err(&pdev->dev, "get wrong master id\n"); + return -EINVAL; } + mxs_saif[saif->id] = saif; + pinctrl = devm_pinctrl_get_select_default(&pdev->dev); if (IS_ERR(pinctrl)) { ret = PTR_ERR(pinctrl); @@ -677,12 +699,19 @@ static int mxs_saif_probe(struct platform_device *pdev) dmares = platform_get_resource(pdev, IORESOURCE_DMA, 0); if (!dmares) { - ret = -ENODEV; - dev_err(&pdev->dev, "failed to get dma resource: %d\n", - ret); - goto failed_get_resource; + /* + * TODO: This is a temporary solution and should be changed + * to use generic DMA binding later when the helplers get in. + */ + ret = of_property_read_u32(np, "fsl,saif-dma-channel", + &saif->dma_param.chan_num); + if (ret) { + dev_err(&pdev->dev, "failed to get dma channel\n"); + goto failed_get_resource; + } + } else { + saif->dma_param.chan_num = dmares->start; } - saif->dma_param.chan_num = dmares->start; saif->irq = platform_get_irq(pdev, 0); if (saif->irq < 0) { @@ -716,24 +745,14 @@ static int mxs_saif_probe(struct platform_device *pdev) goto failed_get_resource; } - saif->soc_platform_pdev = platform_device_alloc( - "mxs-pcm-audio", pdev->id); - if (!saif->soc_platform_pdev) { - ret = -ENOMEM; - goto failed_pdev_alloc; - } - - platform_set_drvdata(saif->soc_platform_pdev, saif); - ret = platform_device_add(saif->soc_platform_pdev); + ret = mxs_pcm_platform_register(&pdev->dev); if (ret) { - dev_err(&pdev->dev, "failed to add soc platform device\n"); - goto failed_pdev_add; + dev_err(&pdev->dev, "register PCM failed: %d\n", ret); + goto failed_pdev_alloc; } return 0; -failed_pdev_add: - platform_device_put(saif->soc_platform_pdev); failed_pdev_alloc: snd_soc_unregister_dai(&pdev->dev); failed_get_resource: @@ -746,13 +765,19 @@ static int __devexit mxs_saif_remove(struct platform_device *pdev) { struct mxs_saif *saif = platform_get_drvdata(pdev); - platform_device_unregister(saif->soc_platform_pdev); + mxs_pcm_platform_unregister(&pdev->dev); snd_soc_unregister_dai(&pdev->dev); clk_put(saif->clk); return 0; } +static const struct of_device_id mxs_saif_dt_ids[] = { + { .compatible = "fsl,imx28-saif", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, mxs_saif_dt_ids); + static struct platform_driver mxs_saif_driver = { .probe = mxs_saif_probe, .remove = __devexit_p(mxs_saif_remove), @@ -760,6 +785,7 @@ static struct platform_driver mxs_saif_driver = { .driver = { .name = "mxs-saif", .owner = THIS_MODULE, + .of_match_table = mxs_saif_dt_ids, }, }; diff --git a/sound/soc/mxs/mxs-saif.h b/sound/soc/mxs/mxs-saif.h index 12c91e4eb94..3cb342e5bc9 100644 --- a/sound/soc/mxs/mxs-saif.h +++ b/sound/soc/mxs/mxs-saif.h @@ -123,7 +123,6 @@ struct mxs_saif { unsigned int cur_rate; unsigned int ongoing; - struct platform_device *soc_platform_pdev; u32 fifo_underrun; u32 fifo_overrun; }; diff --git a/sound/soc/mxs/mxs-sgtl5000.c b/sound/soc/mxs/mxs-sgtl5000.c index 60f052b7cf2..3e6e8764b2e 100644 --- a/sound/soc/mxs/mxs-sgtl5000.c +++ b/sound/soc/mxs/mxs-sgtl5000.c @@ -18,6 +18,8 @@ #include <linux/module.h> #include <linux/device.h> +#include <linux/of.h> +#include <linux/of_device.h> #include <sound/core.h> #include <sound/pcm.h> #include <sound/soc.h> @@ -90,7 +92,7 @@ static struct snd_soc_dai_link mxs_sgtl5000_dai[] = { .codec_dai_name = "sgtl5000", .codec_name = "sgtl5000.0-000a", .cpu_dai_name = "mxs-saif.0", - .platform_name = "mxs-pcm-audio.0", + .platform_name = "mxs-saif.0", .ops = &mxs_sgtl5000_hifi_ops, }, { .name = "HiFi Rx", @@ -98,7 +100,7 @@ static struct snd_soc_dai_link mxs_sgtl5000_dai[] = { .codec_dai_name = "sgtl5000", .codec_name = "sgtl5000.0-000a", .cpu_dai_name = "mxs-saif.1", - .platform_name = "mxs-pcm-audio.1", + .platform_name = "mxs-saif.1", .ops = &mxs_sgtl5000_hifi_ops, }, }; @@ -110,11 +112,48 @@ static struct snd_soc_card mxs_sgtl5000 = { .num_links = ARRAY_SIZE(mxs_sgtl5000_dai), }; +static int __devinit mxs_sgtl5000_probe_dt(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct device_node *saif_np[2], *codec_np; + int i, ret = 0; + + if (!np) + return 1; /* no device tree */ + + saif_np[0] = of_parse_phandle(np, "saif-controllers", 0); + saif_np[1] = of_parse_phandle(np, "saif-controllers", 1); + codec_np = of_parse_phandle(np, "audio-codec", 0); + if (!saif_np[0] || !saif_np[1] || !codec_np) { + dev_err(&pdev->dev, "phandle missing or invalid\n"); + return -EINVAL; + } + + for (i = 0; i < 2; i++) { + mxs_sgtl5000_dai[i].codec_name = NULL; + mxs_sgtl5000_dai[i].codec_of_node = codec_np; + mxs_sgtl5000_dai[i].cpu_dai_name = NULL; + mxs_sgtl5000_dai[i].cpu_dai_of_node = saif_np[i]; + mxs_sgtl5000_dai[i].platform_name = NULL; + mxs_sgtl5000_dai[i].platform_of_node = saif_np[i]; + } + + of_node_put(codec_np); + of_node_put(saif_np[0]); + of_node_put(saif_np[1]); + + return ret; +} + static int __devinit mxs_sgtl5000_probe(struct platform_device *pdev) { struct snd_soc_card *card = &mxs_sgtl5000; int ret; + ret = mxs_sgtl5000_probe_dt(pdev); + if (ret < 0) + return ret; + /* * Set an init clock(11.28Mhz) for sgtl5000 initialization(i2c r/w). * The Sgtl5000 sysclk is derived from saif0 mclk and it's range @@ -148,10 +187,17 @@ static int __devexit mxs_sgtl5000_remove(struct platform_device *pdev) return 0; } +static const struct of_device_id mxs_sgtl5000_dt_ids[] = { + { .compatible = "fsl,mxs-audio-sgtl5000", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, mxs_sgtl5000_dt_ids); + static struct platform_driver mxs_sgtl5000_audio_driver = { .driver = { .name = "mxs-sgtl5000", .owner = THIS_MODULE, + .of_match_table = mxs_sgtl5000_dt_ids, }, .probe = mxs_sgtl5000_probe, .remove = __devexit_p(mxs_sgtl5000_remove), diff --git a/sound/soc/omap/Kconfig b/sound/soc/omap/Kconfig index deafbfaacdb..9ccfa5e1c11 100644 --- a/sound/soc/omap/Kconfig +++ b/sound/soc/omap/Kconfig @@ -113,6 +113,7 @@ config SND_OMAP_SOC_OMAP4_HDMI tristate "SoC Audio support for Texas Instruments OMAP4 HDMI" depends on SND_OMAP_SOC && OMAP4_DSS_HDMI && OMAP2_DSS && ARCH_OMAP4 select SND_OMAP_SOC_HDMI + select SND_SOC_OMAP_HDMI_CODEC help Say Y if you want to add support for SoC HDMI audio on Texas Instruments OMAP4 chips diff --git a/sound/soc/pxa/pxa-ssp.c b/sound/soc/pxa/pxa-ssp.c index fd04ce13903..1c2aa7fab3f 100644 --- a/sound/soc/pxa/pxa-ssp.c +++ b/sound/soc/pxa/pxa-ssp.c @@ -85,14 +85,12 @@ struct pxa2xx_pcm_dma_data { char name[20]; }; -static struct pxa2xx_pcm_dma_params * -pxa_ssp_get_dma_params(struct ssp_device *ssp, int width4, int out) +static void pxa_ssp_set_dma_params(struct ssp_device *ssp, int width4, + int out, struct pxa2xx_pcm_dma_params *dma_data) { struct pxa2xx_pcm_dma_data *dma; - dma = kzalloc(sizeof(struct pxa2xx_pcm_dma_data), GFP_KERNEL); - if (dma == NULL) - return NULL; + dma = container_of(dma_data, struct pxa2xx_pcm_dma_data, params); snprintf(dma->name, 20, "SSP%d PCM %s %s", ssp->port_id, width4 ? "32-bit" : "16-bit", out ? "out" : "in"); @@ -103,8 +101,6 @@ pxa_ssp_get_dma_params(struct ssp_device *ssp, int width4, int out) (DCMD_INCTRGADDR | DCMD_FLOWSRC)) | (width4 ? DCMD_WIDTH4 : DCMD_WIDTH2) | DCMD_BURST16; dma->params.dev_addr = ssp->phys_base + SSDR; - - return &dma->params; } static int pxa_ssp_startup(struct snd_pcm_substream *substream, @@ -112,6 +108,7 @@ static int pxa_ssp_startup(struct snd_pcm_substream *substream, { struct ssp_priv *priv = snd_soc_dai_get_drvdata(cpu_dai); struct ssp_device *ssp = priv->ssp; + struct pxa2xx_pcm_dma_data *dma; int ret = 0; if (!cpu_dai->active) { @@ -119,8 +116,10 @@ static int pxa_ssp_startup(struct snd_pcm_substream *substream, pxa_ssp_disable(ssp); } - kfree(snd_soc_dai_get_dma_data(cpu_dai, substream)); - snd_soc_dai_set_dma_data(cpu_dai, substream, NULL); + dma = kzalloc(sizeof(struct pxa2xx_pcm_dma_data), GFP_KERNEL); + if (!dma) + return -ENOMEM; + snd_soc_dai_set_dma_data(cpu_dai, substream, &dma->params); return ret; } @@ -573,18 +572,13 @@ static int pxa_ssp_hw_params(struct snd_pcm_substream *substream, dma_data = snd_soc_dai_get_dma_data(cpu_dai, substream); - /* generate correct DMA params */ - kfree(dma_data); - /* Network mode with one active slot (ttsa == 1) can be used * to force 16-bit frame width on the wire (for S16_LE), even * with two channels. Use 16-bit DMA transfers for this case. */ - dma_data = pxa_ssp_get_dma_params(ssp, - ((chn == 2) && (ttsa != 1)) || (width == 32), - substream->stream == SNDRV_PCM_STREAM_PLAYBACK); - - snd_soc_dai_set_dma_data(cpu_dai, substream, dma_data); + pxa_ssp_set_dma_params(ssp, + ((chn == 2) && (ttsa != 1)) || (width == 32), + substream->stream == SNDRV_PCM_STREAM_PLAYBACK, dma_data); /* we can only change the settings if the port is not in use */ if (pxa_ssp_read_reg(ssp, SSCR0) & SSCR0_SSE) diff --git a/sound/soc/pxa/pxa2xx-i2s.c b/sound/soc/pxa/pxa2xx-i2s.c index d08583790d2..3075a426124 100644 --- a/sound/soc/pxa/pxa2xx-i2s.c +++ b/sound/soc/pxa/pxa2xx-i2s.c @@ -166,7 +166,7 @@ static int pxa2xx_i2s_hw_params(struct snd_pcm_substream *substream, struct pxa2xx_pcm_dma_params *dma_data; BUG_ON(IS_ERR(clk_i2s)); - clk_enable(clk_i2s); + clk_prepare_enable(clk_i2s); clk_ena = 1; pxa_i2s_wait(); @@ -259,7 +259,7 @@ static void pxa2xx_i2s_shutdown(struct snd_pcm_substream *substream, SACR0 &= ~SACR0_ENB; pxa_i2s_wait(); if (clk_ena) { - clk_disable(clk_i2s); + clk_disable_unprepare(clk_i2s); clk_ena = 0; } } diff --git a/sound/soc/samsung/littlemill.c b/sound/soc/samsung/littlemill.c index e7416851bf7..c82c646b8a0 100644 --- a/sound/soc/samsung/littlemill.c +++ b/sound/soc/samsung/littlemill.c @@ -23,10 +23,10 @@ static int littlemill_set_bias_level(struct snd_soc_card *card, struct snd_soc_dapm_context *dapm, enum snd_soc_bias_level level) { - struct snd_soc_dai *codec_dai = card->rtd[0].codec_dai; + struct snd_soc_dai *aif1_dai = card->rtd[0].codec_dai; int ret; - if (dapm->dev != codec_dai->dev) + if (dapm->dev != aif1_dai->dev) return 0; switch (level) { @@ -36,7 +36,7 @@ static int littlemill_set_bias_level(struct snd_soc_card *card, * then do so now, otherwise these are noops. */ if (dapm->bias_level == SND_SOC_BIAS_STANDBY) { - ret = snd_soc_dai_set_pll(codec_dai, WM8994_FLL1, + ret = snd_soc_dai_set_pll(aif1_dai, WM8994_FLL1, WM8994_FLL_SRC_MCLK2, 32768, sample_rate * 512); if (ret < 0) { @@ -44,7 +44,7 @@ static int littlemill_set_bias_level(struct snd_soc_card *card, return ret; } - ret = snd_soc_dai_set_sysclk(codec_dai, + ret = snd_soc_dai_set_sysclk(aif1_dai, WM8994_SYSCLK_FLL1, sample_rate * 512, SND_SOC_CLOCK_IN); @@ -66,25 +66,25 @@ static int littlemill_set_bias_level_post(struct snd_soc_card *card, struct snd_soc_dapm_context *dapm, enum snd_soc_bias_level level) { - struct snd_soc_dai *codec_dai = card->rtd[0].codec_dai; + struct snd_soc_dai *aif1_dai = card->rtd[0].codec_dai; int ret; - if (dapm->dev != codec_dai->dev) + if (dapm->dev != aif1_dai->dev) return 0; switch (level) { case SND_SOC_BIAS_STANDBY: - ret = snd_soc_dai_set_sysclk(codec_dai, WM8994_SYSCLK_MCLK2, + ret = snd_soc_dai_set_sysclk(aif1_dai, WM8994_SYSCLK_MCLK2, 32768, SND_SOC_CLOCK_IN); if (ret < 0) { - pr_err("Failed to switch away from FLL: %d\n", ret); + pr_err("Failed to switch away from FLL1: %d\n", ret); return ret; } - ret = snd_soc_dai_set_pll(codec_dai, WM8994_FLL1, + ret = snd_soc_dai_set_pll(aif1_dai, WM8994_FLL1, 0, 0, 0); if (ret < 0) { - pr_err("Failed to stop FLL: %d\n", ret); + pr_err("Failed to stop FLL1: %d\n", ret); return ret; } break; @@ -131,6 +131,14 @@ static struct snd_soc_ops littlemill_ops = { .hw_params = littlemill_hw_params, }; +static const struct snd_soc_pcm_stream baseband_params = { + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .rate_min = 8000, + .rate_max = 8000, + .channels_min = 2, + .channels_max = 2, +}; + static struct snd_soc_dai_link littlemill_dai[] = { { .name = "CPU", @@ -143,13 +151,75 @@ static struct snd_soc_dai_link littlemill_dai[] = { | SND_SOC_DAIFMT_CBM_CFM, .ops = &littlemill_ops, }, + { + .name = "Baseband", + .stream_name = "Baseband", + .cpu_dai_name = "wm8994-aif2", + .codec_dai_name = "wm1250-ev1", + .codec_name = "wm1250-ev1.1-0027", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBM_CFM, + .ignore_suspend = 1, + .params = &baseband_params, + }, }; +static int bbclk_ev(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_card *card = w->dapm->card; + struct snd_soc_dai *aif2_dai = card->rtd[1].cpu_dai; + int ret; + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + ret = snd_soc_dai_set_pll(aif2_dai, WM8994_FLL2, + WM8994_FLL_SRC_BCLK, 64 * 8000, + 8000 * 256); + if (ret < 0) { + pr_err("Failed to start FLL: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_sysclk(aif2_dai, WM8994_SYSCLK_FLL2, + 8000 * 256, + SND_SOC_CLOCK_IN); + if (ret < 0) { + pr_err("Failed to set SYSCLK: %d\n", ret); + return ret; + } + break; + case SND_SOC_DAPM_POST_PMD: + ret = snd_soc_dai_set_sysclk(aif2_dai, WM8994_SYSCLK_MCLK2, + 32768, SND_SOC_CLOCK_IN); + if (ret < 0) { + pr_err("Failed to switch away from FLL2: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_pll(aif2_dai, WM8994_FLL2, + 0, 0, 0); + if (ret < 0) { + pr_err("Failed to stop FLL2: %d\n", ret); + return ret; + } + break; + default: + return -EINVAL; + } + + return 0; +} + static struct snd_soc_dapm_widget widgets[] = { SND_SOC_DAPM_HP("Headphone", NULL), SND_SOC_DAPM_MIC("AMIC", NULL), SND_SOC_DAPM_MIC("DMIC", NULL), + + SND_SOC_DAPM_SUPPLY_S("Baseband Clock", -1, SND_SOC_NOPM, 0, 0, + bbclk_ev, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), }; static struct snd_soc_dapm_route audio_paths[] = { @@ -162,6 +232,8 @@ static struct snd_soc_dapm_route audio_paths[] = { { "DMIC", NULL, "MICBIAS2" }, /* Default for DMICBIAS jumper */ { "DMIC1DAT", NULL, "DMIC" }, { "DMIC2DAT", NULL, "DMIC" }, + + { "AIF2CLK", NULL, "Baseband Clock" }, }; static struct snd_soc_jack littlemill_headset; @@ -169,10 +241,16 @@ static struct snd_soc_jack littlemill_headset; static int littlemill_late_probe(struct snd_soc_card *card) { struct snd_soc_codec *codec = card->rtd[0].codec; - struct snd_soc_dai *codec_dai = card->rtd[0].codec_dai; + struct snd_soc_dai *aif1_dai = card->rtd[0].codec_dai; + struct snd_soc_dai *aif2_dai = card->rtd[1].cpu_dai; int ret; - ret = snd_soc_dai_set_sysclk(codec_dai, WM8994_SYSCLK_MCLK2, + ret = snd_soc_dai_set_sysclk(aif1_dai, WM8994_SYSCLK_MCLK2, + 32768, SND_SOC_CLOCK_IN); + if (ret < 0) + return ret; + + ret = snd_soc_dai_set_sysclk(aif2_dai, WM8994_SYSCLK_MCLK2, 32768, SND_SOC_CLOCK_IN); if (ret < 0) return ret; diff --git a/sound/soc/samsung/lowland.c b/sound/soc/samsung/lowland.c index 4adff934f77..6abf341c4a2 100644 --- a/sound/soc/samsung/lowland.c +++ b/sound/soc/samsung/lowland.c @@ -21,33 +21,6 @@ #define MCLK1_RATE (44100 * 512) #define CLKOUT_RATE (44100 * 256) -static int lowland_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 ret; - - ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S - | SND_SOC_DAIFMT_NB_NF - | SND_SOC_DAIFMT_CBM_CFM); - if (ret < 0) - return ret; - - ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S - | SND_SOC_DAIFMT_NB_NF - | SND_SOC_DAIFMT_CBM_CFM); - if (ret < 0) - return ret; - - return 0; -} - -static struct snd_soc_ops lowland_ops = { - .hw_params = lowland_hw_params, -}; - static struct snd_soc_jack lowland_headset; /* Headset jack detection DAPM pins */ @@ -101,6 +74,25 @@ static int lowland_wm5100_init(struct snd_soc_pcm_runtime *rtd) return 0; } +static int lowland_wm9081_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_codec *codec = rtd->codec; + + snd_soc_dapm_nc_pin(&codec->dapm, "LINEOUT"); + + /* At any time the WM9081 is active it will have this clock */ + return snd_soc_codec_set_sysclk(codec, WM9081_SYSCLK_MCLK, 0, + CLKOUT_RATE, 0); +} + +static const struct snd_soc_pcm_stream sub_params = { + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .rate_min = 44100, + .rate_max = 44100, + .channels_min = 2, + .channels_max = 2, +}; + static struct snd_soc_dai_link lowland_dai[] = { { .name = "CPU", @@ -109,7 +101,8 @@ static struct snd_soc_dai_link lowland_dai[] = { .codec_dai_name = "wm5100-aif1", .platform_name = "samsung-audio", .codec_name = "wm5100.1-001a", - .ops = &lowland_ops, + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM, .init = lowland_wm5100_init, }, { @@ -118,24 +111,20 @@ static struct snd_soc_dai_link lowland_dai[] = { .cpu_dai_name = "wm5100-aif2", .codec_dai_name = "wm1250-ev1", .codec_name = "wm1250-ev1.1-0027", - .ops = &lowland_ops, + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM, .ignore_suspend = 1, }, -}; - -static int lowland_wm9081_init(struct snd_soc_dapm_context *dapm) -{ - snd_soc_dapm_nc_pin(dapm, "LINEOUT"); - - /* At any time the WM9081 is active it will have this clock */ - return snd_soc_codec_set_sysclk(dapm->codec, WM9081_SYSCLK_MCLK, 0, - CLKOUT_RATE, 0); -} - -static struct snd_soc_aux_dev lowland_aux_dev[] = { { - .name = "wm9081", + .name = "Sub Speaker", + .stream_name = "Sub Speaker", + .cpu_dai_name = "wm5100-aif3", + .codec_dai_name = "wm9081-hifi", .codec_name = "wm9081.1-006c", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM, + .ignore_suspend = 1, + .params = &sub_params, .init = lowland_wm9081_init, }, }; @@ -180,8 +169,6 @@ static struct snd_soc_card lowland = { .owner = THIS_MODULE, .dai_link = lowland_dai, .num_links = ARRAY_SIZE(lowland_dai), - .aux_dev = lowland_aux_dev, - .num_aux_devs = ARRAY_SIZE(lowland_aux_dev), .codec_conf = lowland_codec_conf, .num_configs = ARRAY_SIZE(lowland_codec_conf), diff --git a/sound/soc/samsung/speyside.c b/sound/soc/samsung/speyside.c index f9ab7707a3e..a4a9fc7e8c7 100644 --- a/sound/soc/samsung/speyside.c +++ b/sound/soc/samsung/speyside.c @@ -92,33 +92,6 @@ static int speyside_set_bias_level_post(struct snd_soc_card *card, return 0; } -static int speyside_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 ret; - - ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S - | SND_SOC_DAIFMT_NB_NF - | SND_SOC_DAIFMT_CBM_CFM); - if (ret < 0) - return ret; - - ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S - | SND_SOC_DAIFMT_NB_NF - | SND_SOC_DAIFMT_CBM_CFM); - if (ret < 0) - return ret; - - return 0; -} - -static struct snd_soc_ops speyside_ops = { - .hw_params = speyside_hw_params, -}; - static struct snd_soc_jack speyside_headset; /* Headset jack detection DAPM pins */ @@ -208,7 +181,8 @@ static struct snd_soc_dai_link speyside_dai[] = { .platform_name = "samsung-audio", .codec_name = "wm8996.1-001a", .init = speyside_wm8996_init, - .ops = &speyside_ops, + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBM_CFM, }, { .name = "Baseband", @@ -216,7 +190,8 @@ static struct snd_soc_dai_link speyside_dai[] = { .cpu_dai_name = "wm8996-aif2", .codec_dai_name = "wm1250-ev1", .codec_name = "wm1250-ev1.1-0027", - .ops = &speyside_ops, + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBM_CFM, .ignore_suspend = 1, }, }; diff --git a/sound/soc/sh/Kconfig b/sound/soc/sh/Kconfig index d8e06a607a2..6bcb1164d59 100644 --- a/sound/soc/sh/Kconfig +++ b/sound/soc/sh/Kconfig @@ -22,6 +22,7 @@ config SND_SOC_SH4_SSI config SND_SOC_SH4_FSI tristate "SH4 FSI support" + select SND_SIMPLE_CARD help This option enables FSI sound support @@ -46,29 +47,6 @@ config SND_SH7760_AC97 This option enables generic sound support for the first AC97 unit of the SH7760. -config SND_FSI_AK4642 - tristate "FSI-AK4642 sound support" - depends on SND_SOC_SH4_FSI && I2C - select SND_SOC_AK4642 - help - This option enables generic sound support for the - FSI - AK4642 unit - -config SND_FSI_DA7210 - tristate "FSI-DA7210 sound support" - depends on SND_SOC_SH4_FSI && I2C - select SND_SOC_DA7210 - help - This option enables generic sound support for the - FSI - DA7210 unit - -config SND_FSI_HDMI - tristate "FSI-HDMI sound support" - depends on SND_SOC_SH4_FSI && FB_SH_MOBILE_HDMI - help - This option enables generic sound support for the - FSI - HDMI unit - config SND_SIU_MIGOR tristate "SIU sound support on Migo-R" depends on SH_MIGOR diff --git a/sound/soc/sh/Makefile b/sound/soc/sh/Makefile index 94476d4c0fd..849b387d17d 100644 --- a/sound/soc/sh/Makefile +++ b/sound/soc/sh/Makefile @@ -14,13 +14,7 @@ obj-$(CONFIG_SND_SOC_SH4_SIU) += snd-soc-siu.o ## boards snd-soc-sh7760-ac97-objs := sh7760-ac97.o -snd-soc-fsi-ak4642-objs := fsi-ak4642.o -snd-soc-fsi-da7210-objs := fsi-da7210.o -snd-soc-fsi-hdmi-objs := fsi-hdmi.o snd-soc-migor-objs := migor.o obj-$(CONFIG_SND_SH7760_AC97) += snd-soc-sh7760-ac97.o -obj-$(CONFIG_SND_FSI_AK4642) += snd-soc-fsi-ak4642.o -obj-$(CONFIG_SND_FSI_DA7210) += snd-soc-fsi-da7210.o -obj-$(CONFIG_SND_FSI_HDMI) += snd-soc-fsi-hdmi.o obj-$(CONFIG_SND_SIU_MIGOR) += snd-soc-migor.o diff --git a/sound/soc/sh/fsi-ak4642.c b/sound/soc/sh/fsi-ak4642.c deleted file mode 100644 index 97f540aabbd..00000000000 --- a/sound/soc/sh/fsi-ak4642.c +++ /dev/null @@ -1,108 +0,0 @@ -/* - * FSI-AK464x sound support for ms7724se - * - * Copyright (C) 2009 Renesas Solutions Corp. - * Kuninori Morimoto <morimoto.kuninori@renesas.com> - * - * This file is subject to the terms and conditions of the GNU General Public - * License. See the file "COPYING" in the main directory of this archive - * for more details. - */ - -#include <linux/platform_device.h> -#include <linux/module.h> -#include <sound/sh_fsi.h> - -struct fsi_ak4642_data { - const char *name; - const char *card; - const char *cpu_dai; - const char *codec; - const char *platform; - int id; -}; - -static int fsi_ak4642_dai_init(struct snd_soc_pcm_runtime *rtd) -{ - struct snd_soc_dai *codec = rtd->codec_dai; - struct snd_soc_dai *cpu = rtd->cpu_dai; - int ret; - - ret = snd_soc_dai_set_fmt(codec, SND_SOC_DAIFMT_LEFT_J | - SND_SOC_DAIFMT_CBM_CFM); - if (ret < 0) - return ret; - - ret = snd_soc_dai_set_sysclk(codec, 0, 11289600, 0); - if (ret < 0) - return ret; - - ret = snd_soc_dai_set_fmt(cpu, SND_SOC_DAIFMT_LEFT_J | - SND_SOC_DAIFMT_CBS_CFS); - - return ret; -} - -static struct snd_soc_dai_link fsi_dai_link = { - .codec_dai_name = "ak4642-hifi", - .init = fsi_ak4642_dai_init, -}; - -static struct snd_soc_card fsi_soc_card = { - .owner = THIS_MODULE, - .dai_link = &fsi_dai_link, - .num_links = 1, -}; - -static struct platform_device *fsi_snd_device; - -static int fsi_ak4642_probe(struct platform_device *pdev) -{ - int ret = -ENOMEM; - struct fsi_ak4642_info *pinfo = pdev->dev.platform_data; - - if (!pinfo) { - dev_err(&pdev->dev, "no info for fsi ak4642\n"); - goto out; - } - - fsi_snd_device = platform_device_alloc("soc-audio", pinfo->id); - if (!fsi_snd_device) - goto out; - - fsi_dai_link.name = pinfo->name; - fsi_dai_link.stream_name = pinfo->name; - fsi_dai_link.cpu_dai_name = pinfo->cpu_dai; - fsi_dai_link.platform_name = pinfo->platform; - fsi_dai_link.codec_name = pinfo->codec; - fsi_soc_card.name = pinfo->card; - - platform_set_drvdata(fsi_snd_device, &fsi_soc_card); - ret = platform_device_add(fsi_snd_device); - - if (ret) - platform_device_put(fsi_snd_device); - -out: - return ret; -} - -static int fsi_ak4642_remove(struct platform_device *pdev) -{ - platform_device_unregister(fsi_snd_device); - return 0; -} - -static struct platform_driver fsi_ak4642 = { - .driver = { - .name = "fsi-ak4642-audio", - }, - .probe = fsi_ak4642_probe, - .remove = fsi_ak4642_remove, -}; - -module_platform_driver(fsi_ak4642); - -MODULE_LICENSE("GPL"); -MODULE_DESCRIPTION("Generic SH4 FSI-AK4642 sound card"); -MODULE_AUTHOR("Kuninori Morimoto <morimoto.kuninori@renesas.com>"); diff --git a/sound/soc/sh/fsi-da7210.c b/sound/soc/sh/fsi-da7210.c deleted file mode 100644 index 1dd3354c741..00000000000 --- a/sound/soc/sh/fsi-da7210.c +++ /dev/null @@ -1,81 +0,0 @@ -/* - * fsi-da7210.c - * - * Copyright (C) 2009 Renesas Solutions Corp. - * Kuninori Morimoto <morimoto.kuninori@renesas.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/platform_device.h> -#include <linux/module.h> -#include <sound/sh_fsi.h> - -static int fsi_da7210_init(struct snd_soc_pcm_runtime *rtd) -{ - struct snd_soc_dai *codec = rtd->codec_dai; - struct snd_soc_dai *cpu = rtd->cpu_dai; - int ret; - - ret = snd_soc_dai_set_fmt(codec, - SND_SOC_DAIFMT_I2S | - SND_SOC_DAIFMT_CBM_CFM); - if (ret < 0) - return ret; - - ret = snd_soc_dai_set_fmt(cpu, SND_SOC_DAIFMT_I2S | - SND_SOC_DAIFMT_CBS_CFS); - - return ret; -} - -static struct snd_soc_dai_link fsi_da7210_dai = { - .name = "DA7210", - .stream_name = "DA7210", - .cpu_dai_name = "fsib-dai", /* FSI B */ - .codec_dai_name = "da7210-hifi", - .platform_name = "sh_fsi.0", - .codec_name = "da7210-codec.0-001a", - .init = fsi_da7210_init, -}; - -static struct snd_soc_card fsi_soc_card = { - .name = "FSI-DA7210", - .owner = THIS_MODULE, - .dai_link = &fsi_da7210_dai, - .num_links = 1, -}; - -static struct platform_device *fsi_da7210_snd_device; - -static int __init fsi_da7210_sound_init(void) -{ - int ret; - - fsi_da7210_snd_device = platform_device_alloc("soc-audio", FSI_PORT_B); - if (!fsi_da7210_snd_device) - return -ENOMEM; - - platform_set_drvdata(fsi_da7210_snd_device, &fsi_soc_card); - ret = platform_device_add(fsi_da7210_snd_device); - if (ret) - platform_device_put(fsi_da7210_snd_device); - - return ret; -} - -static void __exit fsi_da7210_sound_exit(void) -{ - platform_device_unregister(fsi_da7210_snd_device); -} - -module_init(fsi_da7210_sound_init); -module_exit(fsi_da7210_sound_exit); - -/* Module information */ -MODULE_DESCRIPTION("ALSA SoC FSI DA2710"); -MODULE_AUTHOR("Kuninori Morimoto <morimoto.kuninori@renesas.com>"); -MODULE_LICENSE("GPL"); diff --git a/sound/soc/sh/fsi-hdmi.c b/sound/soc/sh/fsi-hdmi.c deleted file mode 100644 index 6e41908323e..00000000000 --- a/sound/soc/sh/fsi-hdmi.c +++ /dev/null @@ -1,118 +0,0 @@ -/* - * FSI - HDMI sound support - * - * Copyright (C) 2010 Renesas Solutions Corp. - * Kuninori Morimoto <kuninori.morimoto.gx@renesas.com> - * - * This file is subject to the terms and conditions of the GNU General Public - * License. See the file "COPYING" in the main directory of this archive - * for more details. - */ - -#include <linux/platform_device.h> -#include <linux/module.h> -#include <sound/sh_fsi.h> - -struct fsi_hdmi_data { - const char *cpu_dai; - const char *card; - int id; -}; - -static int fsi_hdmi_dai_init(struct snd_soc_pcm_runtime *rtd) -{ - struct snd_soc_dai *cpu = rtd->cpu_dai; - int ret; - - ret = snd_soc_dai_set_fmt(cpu, SND_SOC_DAIFMT_CBM_CFM); - - return ret; -} - -static struct snd_soc_dai_link fsi_dai_link = { - .name = "HDMI", - .stream_name = "HDMI", - .codec_dai_name = "sh_mobile_hdmi-hifi", - .platform_name = "sh_fsi2", - .codec_name = "sh-mobile-hdmi", - .init = fsi_hdmi_dai_init, -}; - -static struct snd_soc_card fsi_soc_card = { - .owner = THIS_MODULE, - .dai_link = &fsi_dai_link, - .num_links = 1, -}; - -static struct platform_device *fsi_snd_device; - -static int fsi_hdmi_probe(struct platform_device *pdev) -{ - int ret = -ENOMEM; - const struct platform_device_id *id_entry; - struct fsi_hdmi_data *pdata; - - id_entry = pdev->id_entry; - if (!id_entry) { - dev_err(&pdev->dev, "unknown fsi hdmi\n"); - return -ENODEV; - } - - pdata = (struct fsi_hdmi_data *)id_entry->driver_data; - - fsi_snd_device = platform_device_alloc("soc-audio", pdata->id); - if (!fsi_snd_device) - goto out; - - fsi_dai_link.cpu_dai_name = pdata->cpu_dai; - fsi_soc_card.name = pdata->card; - - platform_set_drvdata(fsi_snd_device, &fsi_soc_card); - ret = platform_device_add(fsi_snd_device); - - if (ret) - platform_device_put(fsi_snd_device); - -out: - return ret; -} - -static int fsi_hdmi_remove(struct platform_device *pdev) -{ - platform_device_unregister(fsi_snd_device); - return 0; -} - -static struct fsi_hdmi_data fsi2_a_hdmi = { - .cpu_dai = "fsia-dai", - .card = "FSI2A-HDMI", - .id = FSI_PORT_A, -}; - -static struct fsi_hdmi_data fsi2_b_hdmi = { - .cpu_dai = "fsib-dai", - .card = "FSI2B-HDMI", - .id = FSI_PORT_B, -}; - -static struct platform_device_id fsi_id_table[] = { - /* FSI 2 */ - { "sh_fsi2_a_hdmi", (kernel_ulong_t)&fsi2_a_hdmi }, - { "sh_fsi2_b_hdmi", (kernel_ulong_t)&fsi2_b_hdmi }, - {}, -}; - -static struct platform_driver fsi_hdmi = { - .driver = { - .name = "fsi-hdmi-audio", - }, - .probe = fsi_hdmi_probe, - .remove = fsi_hdmi_remove, - .id_table = fsi_id_table, -}; - -module_platform_driver(fsi_hdmi); - -MODULE_LICENSE("GPL"); -MODULE_DESCRIPTION("Generic SH4 FSI-HDMI sound card"); -MODULE_AUTHOR("Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>"); diff --git a/sound/soc/sh/fsi.c b/sound/soc/sh/fsi.c index 74ed2dffbff..7cee22515d9 100644 --- a/sound/soc/sh/fsi.c +++ b/sound/soc/sh/fsi.c @@ -132,6 +132,25 @@ typedef int (*set_rate_func)(struct device *dev, int rate, int enable); /* + * bus options + * + * 0x000000BA + * + * A : sample widtht 16bit setting + * B : sample widtht 24bit setting + */ + +#define SHIFT_16DATA 0 +#define SHIFT_24DATA 4 + +#define PACKAGE_24BITBUS_BACK 0 +#define PACKAGE_24BITBUS_FRONT 1 +#define PACKAGE_16BITBUS_STREAM 2 + +#define BUSOP_SET(s, a) ((a) << SHIFT_ ## s ## DATA) +#define BUSOP_GET(s, a) (((a) >> SHIFT_ ## s ## DATA) & 0xF) + +/* * FSI driver use below type name for variable * * xxx_num : number of data @@ -189,6 +208,11 @@ struct fsi_stream { int oerr_num; /* + * bus options + */ + u32 bus_option; + + /* * thse are initialized by fsi_handler_init() */ struct fsi_stream_handler *handler; @@ -211,8 +235,7 @@ struct fsi_priv { struct fsi_stream playback; struct fsi_stream capture; - u32 do_fmt; - u32 di_fmt; + u32 fmt; int chan_num:16; int clk_master:1; @@ -321,6 +344,10 @@ static void _fsi_master_mask_set(struct fsi_master *master, /* * basic function */ +static int fsi_version(struct fsi_master *master) +{ + return master->core->ver; +} static struct fsi_master *fsi_get_master(struct fsi_priv *fsi) { @@ -495,6 +522,7 @@ static void fsi_stream_init(struct fsi_priv *fsi, io->period_samples = fsi_frame2sample(fsi, runtime->period_size); io->period_pos = 0; io->sample_width = samples_to_bytes(runtime, 1); + io->bus_option = 0; io->oerr_num = -1; /* ignore 1st err */ io->uerr_num = -1; /* ignore 1st err */ fsi_stream_handler_call(io, init, fsi, io); @@ -522,6 +550,7 @@ static void fsi_stream_quit(struct fsi_priv *fsi, struct fsi_stream *io) io->period_samples = 0; io->period_pos = 0; io->sample_width = 0; + io->bus_option = 0; io->oerr_num = 0; io->uerr_num = 0; spin_unlock_irqrestore(&master->lock, flags); @@ -581,6 +610,53 @@ static int fsi_stream_remove(struct fsi_priv *fsi) } /* + * format/bus/dma setting + */ +static void fsi_format_bus_setup(struct fsi_priv *fsi, struct fsi_stream *io, + u32 bus, struct device *dev) +{ + struct fsi_master *master = fsi_get_master(fsi); + int is_play = fsi_stream_is_play(fsi, io); + u32 fmt = fsi->fmt; + + if (fsi_version(master) >= 2) { + u32 dma = 0; + + /* + * FSI2 needs DMA/Bus setting + */ + switch (bus) { + case PACKAGE_24BITBUS_FRONT: + fmt |= CR_BWS_24; + dma |= VDMD_FRONT; + dev_dbg(dev, "24bit bus / package in front\n"); + break; + case PACKAGE_16BITBUS_STREAM: + fmt |= CR_BWS_16; + dma |= VDMD_STREAM; + dev_dbg(dev, "16bit bus / stream mode\n"); + break; + case PACKAGE_24BITBUS_BACK: + default: + fmt |= CR_BWS_24; + dma |= VDMD_BACK; + dev_dbg(dev, "24bit bus / package in back\n"); + break; + } + + if (is_play) + fsi_reg_write(fsi, OUT_DMAC, dma); + else + fsi_reg_write(fsi, IN_DMAC, dma); + } + + if (is_play) + fsi_reg_write(fsi, DO_FMT, fmt); + else + fsi_reg_write(fsi, DI_FMT, fmt); +} + +/* * irq function */ @@ -629,11 +705,6 @@ static void fsi_spdif_clk_ctrl(struct fsi_priv *fsi, int enable) struct fsi_master *master = fsi_get_master(fsi); u32 mask, val; - if (master->core->ver < 2) { - pr_err("fsi: register access err (%s)\n", __func__); - return; - } - mask = BP | SE; val = enable ? mask : 0; @@ -648,9 +719,7 @@ static void fsi_spdif_clk_ctrl(struct fsi_priv *fsi, int enable) static int fsi_set_master_clk(struct device *dev, struct fsi_priv *fsi, long rate, int enable) { - struct fsi_master *master = fsi_get_master(fsi); set_rate_func set_rate = fsi_get_info_set_rate(fsi); - int fsi_ver = master->core->ver; int ret; if (!set_rate) @@ -682,10 +751,7 @@ static int fsi_set_master_clk(struct device *dev, struct fsi_priv *fsi, data |= (0x3 << 12); break; case SH_FSI_ACKMD_32: - if (fsi_ver < 2) - dev_err(dev, "unsupported ACKMD\n"); - else - data |= (0x4 << 12); + data |= (0x4 << 12); break; } @@ -708,10 +774,7 @@ static int fsi_set_master_clk(struct device *dev, struct fsi_priv *fsi, data |= (0x4 << 8); break; case SH_FSI_BPFMD_16: - if (fsi_ver < 2) - dev_err(dev, "unsupported ACKMD\n"); - else - data |= (0x7 << 8); + data |= (0x7 << 8); break; } @@ -728,11 +791,26 @@ static int fsi_set_master_clk(struct device *dev, struct fsi_priv *fsi, */ static void fsi_pio_push16(struct fsi_priv *fsi, u8 *_buf, int samples) { - u16 *buf = (u16 *)_buf; + u32 enable_stream = fsi_get_info_flags(fsi) & SH_FSI_ENABLE_STREAM_MODE; int i; - for (i = 0; i < samples; i++) - fsi_reg_write(fsi, DODT, ((u32)*(buf + i) << 8)); + if (enable_stream) { + /* + * stream mode + * see + * fsi_pio_push_init() + */ + u32 *buf = (u32 *)_buf; + + for (i = 0; i < samples / 2; i++) + fsi_reg_write(fsi, DODT, buf[i]); + } else { + /* normal mode */ + u16 *buf = (u16 *)_buf; + + for (i = 0; i < samples; i++) + fsi_reg_write(fsi, DODT, ((u32)*(buf + i) << 8)); + } } static void fsi_pio_pop16(struct fsi_priv *fsi, u8 *_buf, int samples) @@ -872,12 +950,44 @@ static void fsi_pio_start_stop(struct fsi_priv *fsi, struct fsi_stream *io, fsi_master_mask_set(master, CLK_RST, clk, (enable) ? clk : 0); } +static int fsi_pio_push_init(struct fsi_priv *fsi, struct fsi_stream *io) +{ + u32 enable_stream = fsi_get_info_flags(fsi) & SH_FSI_ENABLE_STREAM_MODE; + + /* + * we can use 16bit stream mode + * when "playback" and "16bit data" + * and platform allows "stream mode" + * see + * fsi_pio_push16() + */ + if (enable_stream) + io->bus_option = BUSOP_SET(24, PACKAGE_24BITBUS_BACK) | + BUSOP_SET(16, PACKAGE_16BITBUS_STREAM); + else + io->bus_option = BUSOP_SET(24, PACKAGE_24BITBUS_BACK) | + BUSOP_SET(16, PACKAGE_24BITBUS_BACK); + return 0; +} + +static int fsi_pio_pop_init(struct fsi_priv *fsi, struct fsi_stream *io) +{ + /* + * always 24bit bus, package back when "capture" + */ + io->bus_option = BUSOP_SET(24, PACKAGE_24BITBUS_BACK) | + BUSOP_SET(16, PACKAGE_24BITBUS_BACK); + return 0; +} + static struct fsi_stream_handler fsi_pio_push_handler = { + .init = fsi_pio_push_init, .transfer = fsi_pio_push, .start_stop = fsi_pio_start_stop, }; static struct fsi_stream_handler fsi_pio_pop_handler = { + .init = fsi_pio_pop_init, .transfer = fsi_pio_pop, .start_stop = fsi_pio_start_stop, }; @@ -919,6 +1029,13 @@ static int fsi_dma_init(struct fsi_priv *fsi, struct fsi_stream *io) enum dma_data_direction dir = fsi_stream_is_play(fsi, io) ? DMA_TO_DEVICE : DMA_FROM_DEVICE; + /* + * 24bit data : 24bit bus / package in back + * 16bit data : 16bit bus / stream mode + */ + io->bus_option = BUSOP_SET(24, PACKAGE_24BITBUS_BACK) | + BUSOP_SET(16, PACKAGE_16BITBUS_STREAM); + io->dma = dma_map_single(dai->dev, runtime->dma_area, snd_pcm_lib_buffer_bytes(io->substream), dir); return 0; @@ -1055,25 +1172,9 @@ static int fsi_dma_transfer(struct fsi_priv *fsi, struct fsi_stream *io) static void fsi_dma_push_start_stop(struct fsi_priv *fsi, struct fsi_stream *io, int start) { - u32 bws; - u32 dma; + u32 enable = start ? DMA_ON : 0; - switch (io->sample_width * start) { - case 2: - bws = CR_BWS_16; - dma = VDMD_STREAM | DMA_ON; - break; - case 4: - bws = CR_BWS_24; - dma = VDMD_BACK | DMA_ON; - break; - default: - bws = 0; - dma = 0; - } - - fsi_reg_mask_set(fsi, DO_FMT, CR_BWS_MASK, bws); - fsi_reg_write(fsi, OUT_DMAC, dma); + fsi_reg_mask_set(fsi, OUT_DMAC, DMA_ON, enable); } static int fsi_dma_probe(struct fsi_priv *fsi, struct fsi_stream *io) @@ -1176,8 +1277,6 @@ static int fsi_hw_startup(struct fsi_priv *fsi, struct fsi_stream *io, struct device *dev) { - struct fsi_master *master = fsi_get_master(fsi); - int fsi_ver = master->core->ver; u32 flags = fsi_get_info_flags(fsi); u32 data = 0; @@ -1200,10 +1299,6 @@ static int fsi_hw_startup(struct fsi_priv *fsi, fsi_reg_write(fsi, CKG2, data); - /* set format */ - fsi_reg_write(fsi, DO_FMT, fsi->do_fmt); - fsi_reg_write(fsi, DI_FMT, fsi->di_fmt); - /* spdif ? */ if (fsi_is_spdif(fsi)) { fsi_spdif_clk_ctrl(fsi, 1); @@ -1211,15 +1306,18 @@ static int fsi_hw_startup(struct fsi_priv *fsi, } /* - * FIXME - * - * FSI driver assumed that data package is in-back. - * FSI2 chip can select it. + * get bus settings */ - if (fsi_ver >= 2) { - fsi_reg_write(fsi, OUT_DMAC, (1 << 4)); - fsi_reg_write(fsi, IN_DMAC, (1 << 4)); + data = 0; + switch (io->sample_width) { + case 2: + data = BUSOP_GET(16, io->bus_option); + break; + case 4: + data = BUSOP_GET(24, io->bus_option); + break; } + fsi_format_bus_setup(fsi, io, data, dev); /* irq clear */ fsi_irq_disable(fsi, io); @@ -1243,7 +1341,9 @@ static int fsi_dai_startup(struct snd_pcm_substream *substream, { struct fsi_priv *fsi = fsi_get_priv(substream); - return fsi_hw_startup(fsi, fsi_stream_get(fsi, substream), dai->dev); + fsi->rate = 0; + + return 0; } static void fsi_dai_shutdown(struct snd_pcm_substream *substream, @@ -1251,7 +1351,6 @@ static void fsi_dai_shutdown(struct snd_pcm_substream *substream, { struct fsi_priv *fsi = fsi_get_priv(substream); - fsi_hw_shutdown(fsi, dai->dev); fsi->rate = 0; } @@ -1265,11 +1364,13 @@ static int fsi_dai_trigger(struct snd_pcm_substream *substream, int cmd, switch (cmd) { case SNDRV_PCM_TRIGGER_START: fsi_stream_init(fsi, io, substream); + fsi_hw_startup(fsi, io, dai->dev); ret = fsi_stream_transfer(io); if (0 == ret) fsi_stream_start(fsi, io); break; case SNDRV_PCM_TRIGGER_STOP: + fsi_hw_shutdown(fsi, dai->dev); fsi_stream_stop(fsi, io); fsi_stream_quit(fsi, io); break; @@ -1280,42 +1381,33 @@ static int fsi_dai_trigger(struct snd_pcm_substream *substream, int cmd, static int fsi_set_fmt_dai(struct fsi_priv *fsi, unsigned int fmt) { - u32 data = 0; - switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { case SND_SOC_DAIFMT_I2S: - data = CR_I2S; + fsi->fmt = CR_I2S; fsi->chan_num = 2; break; case SND_SOC_DAIFMT_LEFT_J: - data = CR_PCM; + fsi->fmt = CR_PCM; fsi->chan_num = 2; break; default: return -EINVAL; } - fsi->do_fmt = data; - fsi->di_fmt = data; - return 0; } static int fsi_set_fmt_spdif(struct fsi_priv *fsi) { struct fsi_master *master = fsi_get_master(fsi); - u32 data = 0; - if (master->core->ver < 2) + if (fsi_version(master) < 2) return -EINVAL; - data = CR_BWS_16 | CR_DTMD_SPDIF_PCM | CR_PCM; + fsi->fmt = CR_DTMD_SPDIF_PCM | CR_PCM; fsi->chan_num = 2; fsi->spdif = 1; - fsi->do_fmt = data; - fsi->di_fmt = data; - return 0; } diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index c88d9741b9e..b37ee8077ed 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -39,6 +39,7 @@ #include <sound/pcm.h> #include <sound/pcm_params.h> #include <sound/soc.h> +#include <sound/soc-dpcm.h> #include <sound/initval.h> #define CREATE_TRACE_POINTS @@ -54,7 +55,6 @@ EXPORT_SYMBOL_GPL(snd_soc_debugfs_root); #endif static DEFINE_MUTEX(client_mutex); -static LIST_HEAD(card_list); static LIST_HEAD(dai_list); static LIST_HEAD(platform_list); static LIST_HEAD(codec_list); @@ -465,6 +465,35 @@ static inline void soc_cleanup_card_debugfs(struct snd_soc_card *card) } #endif +struct snd_pcm_substream *snd_soc_get_dai_substream(struct snd_soc_card *card, + const char *dai_link, int stream) +{ + int i; + + for (i = 0; i < card->num_links; i++) { + if (card->rtd[i].dai_link->no_pcm && + !strcmp(card->rtd[i].dai_link->name, dai_link)) + return card->rtd[i].pcm->streams[stream].substream; + } + dev_dbg(card->dev, "failed to find dai link %s\n", dai_link); + return NULL; +} +EXPORT_SYMBOL_GPL(snd_soc_get_dai_substream); + +struct snd_soc_pcm_runtime *snd_soc_get_pcm_runtime(struct snd_soc_card *card, + const char *dai_link) +{ + int i; + + for (i = 0; i < card->num_links; i++) { + if (!strcmp(card->rtd[i].dai_link->name, dai_link)) + return &card->rtd[i]; + } + dev_dbg(card->dev, "failed to find rtd %s\n", dai_link); + return NULL; +} +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) @@ -567,19 +596,16 @@ int snd_soc_suspend(struct device *dev) } for (i = 0; i < card->num_rtd; i++) { - struct snd_soc_dai *codec_dai = card->rtd[i].codec_dai; if (card->rtd[i].dai_link->ignore_suspend) continue; snd_soc_dapm_stream_event(&card->rtd[i], SNDRV_PCM_STREAM_PLAYBACK, - codec_dai, SND_SOC_DAPM_STREAM_SUSPEND); snd_soc_dapm_stream_event(&card->rtd[i], SNDRV_PCM_STREAM_CAPTURE, - codec_dai, SND_SOC_DAPM_STREAM_SUSPEND); } @@ -683,17 +709,16 @@ static void soc_resume_deferred(struct work_struct *work) } for (i = 0; i < card->num_rtd; i++) { - struct snd_soc_dai *codec_dai = card->rtd[i].codec_dai; if (card->rtd[i].dai_link->ignore_suspend) continue; snd_soc_dapm_stream_event(&card->rtd[i], - SNDRV_PCM_STREAM_PLAYBACK, codec_dai, + SNDRV_PCM_STREAM_PLAYBACK, SND_SOC_DAPM_STREAM_RESUME); snd_soc_dapm_stream_event(&card->rtd[i], - SNDRV_PCM_STREAM_CAPTURE, codec_dai, + SNDRV_PCM_STREAM_CAPTURE, SND_SOC_DAPM_STREAM_RESUME); } @@ -783,15 +808,9 @@ static int soc_bind_dai_link(struct snd_soc_card *card, int num) struct snd_soc_dai *codec_dai, *cpu_dai; const char *platform_name; - if (rtd->complete) - return 1; dev_dbg(card->dev, "binding %s at idx %d\n", dai_link->name, num); - /* do we already have the CPU DAI for this link ? */ - if (rtd->cpu_dai) { - goto find_codec; - } - /* no, then find CPU DAI from registered DAIs*/ + /* Find CPU DAI from registered DAIs*/ list_for_each_entry(cpu_dai, &dai_list, list) { if (dai_link->cpu_dai_of_node) { if (cpu_dai->dev->of_node != dai_link->cpu_dai_of_node) @@ -802,18 +821,15 @@ static int soc_bind_dai_link(struct snd_soc_card *card, int num) } rtd->cpu_dai = cpu_dai; - goto find_codec; } - dev_dbg(card->dev, "CPU DAI %s not registered\n", - dai_link->cpu_dai_name); -find_codec: - /* do we already have the CODEC for this link ? */ - if (rtd->codec) { - goto find_platform; + if (!rtd->cpu_dai) { + dev_dbg(card->dev, "CPU DAI %s not registered\n", + dai_link->cpu_dai_name); + return -EPROBE_DEFER; } - /* no, then find CODEC from registered CODECs*/ + /* Find CODEC from registered CODECs */ list_for_each_entry(codec, &codec_list, list) { if (dai_link->codec_of_node) { if (codec->dev->of_node != dai_link->codec_of_node) @@ -835,28 +851,28 @@ find_codec: dai_link->codec_dai_name)) { rtd->codec_dai = codec_dai; - goto find_platform; } } - dev_dbg(card->dev, "CODEC DAI %s not registered\n", - dai_link->codec_dai_name); - goto find_platform; + if (!rtd->codec_dai) { + dev_dbg(card->dev, "CODEC DAI %s not registered\n", + dai_link->codec_dai_name); + return -EPROBE_DEFER; + } } - dev_dbg(card->dev, "CODEC %s not registered\n", - dai_link->codec_name); -find_platform: - /* do we need a platform? */ - if (rtd->platform) - goto out; + if (!rtd->codec) { + dev_dbg(card->dev, "CODEC %s not registered\n", + dai_link->codec_name); + return -EPROBE_DEFER; + } /* if there's no platform we match on the empty platform */ platform_name = dai_link->platform_name; if (!platform_name && !dai_link->platform_of_node) platform_name = "snd-soc-dummy"; - /* no, then find one from the set of registered platforms */ + /* find one from the set of registered platforms */ list_for_each_entry(platform, &platform_list, list) { if (dai_link->platform_of_node) { if (platform->dev->of_node != @@ -868,20 +884,16 @@ find_platform: } rtd->platform = platform; - goto out; } - - dev_dbg(card->dev, "platform %s not registered\n", + if (!rtd->platform) { + dev_dbg(card->dev, "platform %s not registered\n", dai_link->platform_name); - return 0; - -out: - /* mark rtd as complete if we found all 4 of our client devices */ - if (rtd->codec && rtd->codec_dai && rtd->platform && rtd->cpu_dai) { - rtd->complete = 1; - card->num_rtd++; + return -EPROBE_DEFER; } - return 1; + + card->num_rtd++; + + return 0; } static void soc_remove_codec(struct snd_soc_codec *codec) @@ -1068,6 +1080,7 @@ static int soc_probe_platform(struct snd_soc_card *card, { int ret = 0; const struct snd_soc_platform_driver *driver = platform->driver; + struct snd_soc_dai *dai; platform->card = card; platform->dapm.card = card; @@ -1081,6 +1094,14 @@ static int soc_probe_platform(struct snd_soc_card *card, snd_soc_dapm_new_controls(&platform->dapm, driver->dapm_widgets, driver->num_dapm_widgets); + /* Create DAPM widgets for each DAI stream */ + list_for_each_entry(dai, &dai_list, list) { + if (dai->dev != platform->dev) + continue; + + snd_soc_dapm_new_dai_widgets(&platform->dapm, dai); + } + platform->dapm.idle_bias_off = 1; if (driver->probe) { @@ -1170,6 +1191,10 @@ static int soc_post_component_init(struct snd_soc_card *card, rtd->dev->init_name = name; dev_set_drvdata(rtd->dev, rtd); mutex_init(&rtd->pcm_mutex); + INIT_LIST_HEAD(&rtd->dpcm[SNDRV_PCM_STREAM_PLAYBACK].be_clients); + INIT_LIST_HEAD(&rtd->dpcm[SNDRV_PCM_STREAM_CAPTURE].be_clients); + INIT_LIST_HEAD(&rtd->dpcm[SNDRV_PCM_STREAM_PLAYBACK].fe_clients); + INIT_LIST_HEAD(&rtd->dpcm[SNDRV_PCM_STREAM_CAPTURE].fe_clients); ret = device_add(rtd->dev); if (ret < 0) { dev_err(card->dev, @@ -1191,6 +1216,17 @@ static int soc_post_component_init(struct snd_soc_card *card, dev_err(codec->dev, "asoc: failed to add codec sysfs files: %d\n", ret); +#ifdef CONFIG_DEBUG_FS + /* add DPCM sysfs entries */ + if (!dailess && !dai_link->dynamic) + goto out; + + ret = soc_dpcm_debugfs_add(rtd); + if (ret < 0) + dev_err(rtd->dev, "asoc: failed to add dpcm sysfs entries: %d\n", ret); + +out: +#endif return 0; } @@ -1200,14 +1236,15 @@ static int soc_probe_dai_link(struct snd_soc_card *card, int num, int order) struct snd_soc_pcm_runtime *rtd = &card->rtd[num]; struct snd_soc_codec *codec = rtd->codec; struct snd_soc_platform *platform = rtd->platform; - struct snd_soc_dai *codec_dai = rtd->codec_dai, *cpu_dai = rtd->cpu_dai; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_dapm_widget *play_w, *capture_w; int ret; dev_dbg(card->dev, "probe %s dai link %d late %d\n", card->name, num, order); /* config components */ - codec_dai->codec = codec; cpu_dai->platform = platform; codec_dai->card = card; cpu_dai->card = card; @@ -1218,9 +1255,12 @@ static int soc_probe_dai_link(struct snd_soc_card *card, int num, int order) /* probe the cpu_dai */ if (!cpu_dai->probed && cpu_dai->driver->probe_order == order) { + cpu_dai->dapm.card = card; if (!try_module_get(cpu_dai->dev->driver->owner)) return -ENODEV; + snd_soc_dapm_new_dai_widgets(&cpu_dai->dapm, cpu_dai); + if (cpu_dai->driver->probe) { ret = cpu_dai->driver->probe(cpu_dai); if (ret < 0) { @@ -1279,12 +1319,39 @@ static int soc_probe_dai_link(struct snd_soc_card *card, int num, int order) if (ret < 0) pr_warn("asoc: failed to add pmdown_time sysfs:%d\n", ret); - /* create the pcm */ - ret = soc_new_pcm(rtd, num); - if (ret < 0) { - pr_err("asoc: can't create pcm %s :%d\n", - dai_link->stream_name, ret); - return ret; + if (!dai_link->params) { + /* create the pcm */ + ret = soc_new_pcm(rtd, num); + if (ret < 0) { + pr_err("asoc: can't create pcm %s :%d\n", + dai_link->stream_name, ret); + return ret; + } + } else { + /* link the DAI widgets */ + play_w = codec_dai->playback_widget; + capture_w = cpu_dai->capture_widget; + if (play_w && capture_w) { + ret = snd_soc_dapm_new_pcm(card, dai_link->params, + capture_w, play_w); + if (ret != 0) { + dev_err(card->dev, "Can't link %s to %s: %d\n", + play_w->name, capture_w->name, ret); + return ret; + } + } + + play_w = cpu_dai->playback_widget; + capture_w = codec_dai->capture_widget; + if (play_w && capture_w) { + ret = snd_soc_dapm_new_pcm(card, dai_link->params, + capture_w, play_w); + if (ret != 0) { + dev_err(card->dev, "Can't link %s to %s: %d\n", + play_w->name, capture_w->name, ret); + return ret; + } + } } /* add platform data for AC97 devices */ @@ -1334,6 +1401,20 @@ static void soc_unregister_ac97_dai_link(struct snd_soc_codec *codec) } #endif +static int soc_check_aux_dev(struct snd_soc_card *card, int num) +{ + struct snd_soc_aux_dev *aux_dev = &card->aux_dev[num]; + struct snd_soc_codec *codec; + + /* find CODEC from registered CODECs*/ + list_for_each_entry(codec, &codec_list, list) { + if (!strcmp(codec->name, aux_dev->codec_name)) + return 0; + } + + return -EPROBE_DEFER; +} + static int soc_probe_aux_dev(struct snd_soc_card *card, int num) { struct snd_soc_aux_dev *aux_dev = &card->aux_dev[num]; @@ -1354,7 +1435,7 @@ static int soc_probe_aux_dev(struct snd_soc_card *card, int num) } /* codec not found */ dev_err(card->dev, "asoc: codec %s not found", aux_dev->codec_name); - goto out; + return -EPROBE_DEFER; found: ret = soc_probe_codec(card, codec); @@ -1404,29 +1485,28 @@ static int snd_soc_init_codec_cache(struct snd_soc_codec *codec, return 0; } -static void snd_soc_instantiate_card(struct snd_soc_card *card) +static int snd_soc_instantiate_card(struct snd_soc_card *card) { struct snd_soc_codec *codec; struct snd_soc_codec_conf *codec_conf; enum snd_soc_compress_type compress_type; struct snd_soc_dai_link *dai_link; - int ret, i, order; + int ret, i, order, dai_fmt; - mutex_lock(&card->mutex); - - if (card->instantiated) { - mutex_unlock(&card->mutex); - return; - } + mutex_lock_nested(&card->mutex, SND_SOC_CARD_CLASS_INIT); /* bind DAIs */ - for (i = 0; i < card->num_links; i++) - soc_bind_dai_link(card, i); + for (i = 0; i < card->num_links; i++) { + ret = soc_bind_dai_link(card, i); + if (ret != 0) + goto base_error; + } - /* bind completed ? */ - if (card->num_rtd != card->num_links) { - mutex_unlock(&card->mutex); - return; + /* check aux_devs too */ + for (i = 0; i < card->num_aux_devs; i++) { + ret = soc_check_aux_dev(card, i); + if (ret != 0) + goto base_error; } /* initialize the register cache for each available codec */ @@ -1446,10 +1526,8 @@ static void snd_soc_instantiate_card(struct snd_soc_card *card) } } ret = snd_soc_init_codec_cache(codec, compress_type); - if (ret < 0) { - mutex_unlock(&card->mutex); - return; - } + if (ret < 0) + goto base_error; } /* card bind complete so register a sound card */ @@ -1458,8 +1536,7 @@ static void snd_soc_instantiate_card(struct snd_soc_card *card) if (ret < 0) { pr_err("asoc: can't create sound card for card %s: %d\n", card->name, ret); - mutex_unlock(&card->mutex); - return; + goto base_error; } card->snd_card->dev = card->dev; @@ -1523,17 +1600,47 @@ static void snd_soc_instantiate_card(struct snd_soc_card *card) for (i = 0; i < card->num_links; i++) { dai_link = &card->dai_link[i]; + dai_fmt = dai_link->dai_fmt; - if (dai_link->dai_fmt) { + if (dai_fmt) { ret = snd_soc_dai_set_fmt(card->rtd[i].codec_dai, - dai_link->dai_fmt); + dai_fmt); if (ret != 0 && ret != -ENOTSUPP) dev_warn(card->rtd[i].codec_dai->dev, "Failed to set DAI format: %d\n", ret); + } + /* If this is a regular CPU link there will be a platform */ + if (dai_fmt && + (dai_link->platform_name || dai_link->platform_of_node)) { ret = snd_soc_dai_set_fmt(card->rtd[i].cpu_dai, - dai_link->dai_fmt); + dai_fmt); + if (ret != 0 && ret != -ENOTSUPP) + dev_warn(card->rtd[i].cpu_dai->dev, + "Failed to set DAI format: %d\n", + ret); + } else if (dai_fmt) { + /* Flip the polarity for the "CPU" end */ + dai_fmt &= ~SND_SOC_DAIFMT_MASTER_MASK; + switch (dai_link->dai_fmt & + SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + dai_fmt |= SND_SOC_DAIFMT_CBS_CFS; + break; + case SND_SOC_DAIFMT_CBM_CFS: + dai_fmt |= SND_SOC_DAIFMT_CBS_CFM; + break; + case SND_SOC_DAIFMT_CBS_CFM: + dai_fmt |= SND_SOC_DAIFMT_CBM_CFS; + break; + case SND_SOC_DAIFMT_CBS_CFS: + dai_fmt |= SND_SOC_DAIFMT_CBM_CFM; + break; + } + + ret = snd_soc_dai_set_fmt(card->rtd[i].cpu_dai, + dai_fmt); if (ret != 0 && ret != -ENOTSUPP) dev_warn(card->rtd[i].cpu_dai->dev, "Failed to set DAI format: %d\n", @@ -1599,7 +1706,8 @@ static void snd_soc_instantiate_card(struct snd_soc_card *card) card->instantiated = 1; snd_soc_dapm_sync(&card->dapm); mutex_unlock(&card->mutex); - return; + + return 0; probe_aux_dev_err: for (i = 0; i < card->num_aux_devs; i++) @@ -1614,18 +1722,10 @@ card_probe_error: snd_card_free(card->snd_card); +base_error: mutex_unlock(&card->mutex); -} -/* - * Attempt to initialise any uninitialised cards. Must be called with - * client_mutex. - */ -static void snd_soc_instantiate_cards(void) -{ - struct snd_soc_card *card; - list_for_each_entry(card, &card_list, list) - snd_soc_instantiate_card(card); + return ret; } /* probes a new socdev */ @@ -2527,6 +2627,87 @@ int snd_soc_put_volsw(struct snd_kcontrol *kcontrol, 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_codec *codec = 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; + + ucontrol->value.integer.value[0] = + ((snd_soc_read(codec, reg) >> shift) - min) & mask; + + if (snd_soc_volsw_is_stereo(mc)) + ucontrol->value.integer.value[1] = + ((snd_soc_read(codec, reg2) >> rshift) - min) & mask; + + 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_codec *codec = 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 short val, val_mask, val2 = 0; + + val_mask = mask << shift; + val = (ucontrol->value.integer.value[0] + min) & mask; + val = val << shift; + + if (snd_soc_update_bits_locked(codec, reg, val_mask, val)) + return err; + + if (snd_soc_volsw_is_stereo(mc)) { + val_mask = mask << rshift; + val2 = (ucontrol->value.integer.value[1] + min) & mask; + val2 = val2 << rshift; + + if (snd_soc_update_bits_locked(codec, reg2, val_mask, val2)) + return err; + } + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_put_volsw_sx); + +/** * snd_soc_info_volsw_s8 - signed mixer info callback * @kcontrol: mixer control * @uinfo: control element information @@ -2647,99 +2828,6 @@ int snd_soc_limit_volume(struct snd_soc_codec *codec, } EXPORT_SYMBOL_GPL(snd_soc_limit_volume); -/** - * snd_soc_info_volsw_2r_sx - double with tlv and variable data size - * mixer info callback - * @kcontrol: mixer control - * @uinfo: control element information - * - * Returns 0 for success. - */ -int snd_soc_info_volsw_2r_sx(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_info *uinfo) -{ - struct soc_mixer_control *mc = - (struct soc_mixer_control *)kcontrol->private_value; - int max = mc->max; - int min = mc->min; - - uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; - uinfo->count = 2; - uinfo->value.integer.min = 0; - uinfo->value.integer.max = max-min; - - return 0; -} -EXPORT_SYMBOL_GPL(snd_soc_info_volsw_2r_sx); - -/** - * snd_soc_get_volsw_2r_sx - double with tlv and variable data size - * mixer get callback - * @kcontrol: mixer control - * @uinfo: control element information - * - * Returns 0 for success. - */ -int snd_soc_get_volsw_2r_sx(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - struct soc_mixer_control *mc = - (struct soc_mixer_control *)kcontrol->private_value; - struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); - unsigned int mask = (1<<mc->shift)-1; - int min = mc->min; - int val = snd_soc_read(codec, mc->reg) & mask; - int valr = snd_soc_read(codec, mc->rreg) & mask; - - ucontrol->value.integer.value[0] = ((val & 0xff)-min) & mask; - ucontrol->value.integer.value[1] = ((valr & 0xff)-min) & mask; - return 0; -} -EXPORT_SYMBOL_GPL(snd_soc_get_volsw_2r_sx); - -/** - * snd_soc_put_volsw_2r_sx - double with tlv and variable data size - * mixer put callback - * @kcontrol: mixer control - * @uinfo: control element information - * - * Returns 0 for success. - */ -int snd_soc_put_volsw_2r_sx(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - struct soc_mixer_control *mc = - (struct soc_mixer_control *)kcontrol->private_value; - struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); - unsigned int mask = (1<<mc->shift)-1; - int min = mc->min; - int ret; - unsigned int val, valr, oval, ovalr; - - val = ((ucontrol->value.integer.value[0]+min) & 0xff); - val &= mask; - valr = ((ucontrol->value.integer.value[1]+min) & 0xff); - valr &= mask; - - oval = snd_soc_read(codec, mc->reg) & mask; - ovalr = snd_soc_read(codec, mc->rreg) & mask; - - ret = 0; - if (oval != val) { - ret = snd_soc_write(codec, mc->reg, val); - if (ret < 0) - return ret; - } - if (ovalr != valr) { - ret = snd_soc_write(codec, mc->rreg, valr); - if (ret < 0) - return ret; - } - - return 0; -} -EXPORT_SYMBOL_GPL(snd_soc_put_volsw_2r_sx); - int snd_soc_bytes_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { @@ -2850,6 +2938,186 @@ int snd_soc_bytes_put(struct snd_kcontrol *kcontrol, EXPORT_SYMBOL_GPL(snd_soc_bytes_put); /** + * 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 soc_mreg_control *mc = + (struct soc_mreg_control *)kcontrol->private_value; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + unsigned int regbase = mc->regbase; + unsigned int regcount = mc->regcount; + unsigned int regwshift = codec->driver->reg_word_size * 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 long regval; + unsigned int i; + + for (i = 0; i < regcount; i++) { + regval = snd_soc_read(codec, regbase+i) & regwmask; + val |= regval << (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 soc_mreg_control *mc = + (struct soc_mreg_control *)kcontrol->private_value; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + unsigned int regbase = mc->regbase; + unsigned int regcount = mc->regcount; + unsigned int regwshift = codec->driver->reg_word_size * 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_update_bits_locked(codec, 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 soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + unsigned int reg = mc->reg; + unsigned int shift = mc->shift; + unsigned int mask = 1 << shift; + unsigned int invert = mc->invert != 0; + unsigned int val = snd_soc_read(codec, reg) & 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 soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + 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_update_bits_locked(codec, reg, mask, val1); + if (err < 0) + return err; + + err = snd_soc_update_bits_locked(codec, reg, mask, val2); + return err; +} +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 @@ -3048,7 +3316,7 @@ int snd_soc_dai_digital_mute(struct snd_soc_dai *dai, int mute) if (dai->driver && dai->driver->ops->digital_mute) return dai->driver->ops->digital_mute(dai, mute); else - return -EINVAL; + return -ENOTSUPP; } EXPORT_SYMBOL_GPL(snd_soc_dai_digital_mute); @@ -3060,7 +3328,7 @@ EXPORT_SYMBOL_GPL(snd_soc_dai_digital_mute); */ int snd_soc_register_card(struct snd_soc_card *card) { - int i; + int i, ret; if (!card->name || !card->dev) return -EINVAL; @@ -3123,15 +3391,13 @@ int snd_soc_register_card(struct snd_soc_card *card) INIT_LIST_HEAD(&card->dapm_dirty); card->instantiated = 0; mutex_init(&card->mutex); + mutex_init(&card->dapm_mutex); - mutex_lock(&client_mutex); - list_add(&card->list, &card_list); - snd_soc_instantiate_cards(); - mutex_unlock(&client_mutex); - - dev_dbg(card->dev, "Registered card '%s'\n", card->name); + ret = snd_soc_instantiate_card(card); + if (ret != 0) + soc_cleanup_card_debugfs(card); - return 0; + return ret; } EXPORT_SYMBOL_GPL(snd_soc_register_card); @@ -3145,9 +3411,6 @@ int snd_soc_unregister_card(struct snd_soc_card *card) { if (card->instantiated) soc_cleanup_card_resources(card); - mutex_lock(&client_mutex); - list_del(&card->list); - mutex_unlock(&client_mutex); dev_dbg(card->dev, "Unregistered card '%s'\n", card->name); return 0; @@ -3221,6 +3484,7 @@ static inline char *fmt_multiple_name(struct device *dev, int snd_soc_register_dai(struct device *dev, struct snd_soc_dai_driver *dai_drv) { + struct snd_soc_codec *codec; struct snd_soc_dai *dai; dev_dbg(dev, "dai register %s\n", dev_name(dev)); @@ -3238,12 +3502,23 @@ int snd_soc_register_dai(struct device *dev, dai->dev = dev; dai->driver = dai_drv; + dai->dapm.dev = dev; if (!dai->driver->ops) dai->driver->ops = &null_dai_ops; mutex_lock(&client_mutex); + + list_for_each_entry(codec, &codec_list, list) { + if (codec->dev == dev) { + dev_dbg(dev, "Mapped DAI %s to CODEC %s\n", + dai->name, codec->name); + dai->codec = codec; + break; + } + } + list_add(&dai->list, &dai_list); - snd_soc_instantiate_cards(); + mutex_unlock(&client_mutex); pr_debug("Registered DAI '%s'\n", dai->name); @@ -3287,6 +3562,7 @@ EXPORT_SYMBOL_GPL(snd_soc_unregister_dai); int snd_soc_register_dais(struct device *dev, struct snd_soc_dai_driver *dai_drv, size_t count) { + struct snd_soc_codec *codec; struct snd_soc_dai *dai; int i, ret = 0; @@ -3314,19 +3590,28 @@ int snd_soc_register_dais(struct device *dev, dai->id = dai->driver->id; else dai->id = i; + dai->dapm.dev = dev; if (!dai->driver->ops) dai->driver->ops = &null_dai_ops; mutex_lock(&client_mutex); + + list_for_each_entry(codec, &codec_list, list) { + if (codec->dev == dev) { + dev_dbg(dev, "Mapped DAI %s to CODEC %s\n", + dai->name, codec->name); + dai->codec = codec; + break; + } + } + list_add(&dai->list, &dai_list); + mutex_unlock(&client_mutex); pr_debug("Registered DAI '%s'\n", dai->name); } - mutex_lock(&client_mutex); - snd_soc_instantiate_cards(); - mutex_unlock(&client_mutex); return 0; err: @@ -3384,7 +3669,6 @@ int snd_soc_register_platform(struct device *dev, mutex_lock(&client_mutex); list_add(&platform->list, &platform_list); - snd_soc_instantiate_cards(); mutex_unlock(&client_mutex); pr_debug("Registered platform '%s'\n", platform->name); @@ -3534,18 +3818,18 @@ int snd_soc_register_codec(struct device *dev, fixup_codec_formats(&dai_drv[i].capture); } + mutex_lock(&client_mutex); + list_add(&codec->list, &codec_list); + mutex_unlock(&client_mutex); + /* register any DAIs */ if (num_dai) { ret = snd_soc_register_dais(dev, dai_drv, num_dai); if (ret < 0) - goto fail; + dev_err(codec->dev, "Failed to regster DAIs: %d\n", + ret); } - mutex_lock(&client_mutex); - list_add(&codec->list, &codec_list); - snd_soc_instantiate_cards(); - mutex_unlock(&client_mutex); - pr_debug("Registered codec '%s'\n", codec->name); return 0; diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index 1bb6d4a63cd..90ee77d2409 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -52,6 +52,7 @@ static int dapm_up_seq[] = { [snd_soc_dapm_supply] = 1, [snd_soc_dapm_regulator_supply] = 1, [snd_soc_dapm_micbias] = 2, + [snd_soc_dapm_dai_link] = 2, [snd_soc_dapm_dai] = 3, [snd_soc_dapm_aif_in] = 3, [snd_soc_dapm_aif_out] = 3, @@ -90,9 +91,10 @@ static int dapm_down_seq[] = { [snd_soc_dapm_aif_in] = 10, [snd_soc_dapm_aif_out] = 10, [snd_soc_dapm_dai] = 10, - [snd_soc_dapm_regulator_supply] = 11, - [snd_soc_dapm_supply] = 11, - [snd_soc_dapm_post] = 12, + [snd_soc_dapm_dai_link] = 11, + [snd_soc_dapm_regulator_supply] = 12, + [snd_soc_dapm_supply] = 12, + [snd_soc_dapm_post] = 13, }; static void pop_wait(u32 pop_time) @@ -208,7 +210,23 @@ static int soc_widget_write(struct snd_soc_dapm_widget *w, int reg, int val) return -1; } -static int soc_widget_update_bits(struct snd_soc_dapm_widget *w, +static inline void soc_widget_lock(struct snd_soc_dapm_widget *w) +{ + if (w->codec && !w->codec->using_regmap) + mutex_lock(&w->codec->mutex); + else if (w->platform) + mutex_lock(&w->platform->mutex); +} + +static inline void soc_widget_unlock(struct snd_soc_dapm_widget *w) +{ + if (w->codec && !w->codec->using_regmap) + mutex_unlock(&w->codec->mutex); + else if (w->platform) + mutex_unlock(&w->platform->mutex); +} + +static int soc_widget_update_bits_locked(struct snd_soc_dapm_widget *w, unsigned short reg, unsigned int mask, unsigned int value) { bool change; @@ -221,18 +239,24 @@ static int soc_widget_update_bits(struct snd_soc_dapm_widget *w, if (ret != 0) return ret; } else { + soc_widget_lock(w); ret = soc_widget_read(w, reg); - if (ret < 0) + if (ret < 0) { + soc_widget_unlock(w); return ret; + } old = ret; new = (old & ~mask) | (value & mask); change = old != new; if (change) { ret = soc_widget_write(w, reg, new); - if (ret < 0) + if (ret < 0) { + soc_widget_unlock(w); return ret; + } } + soc_widget_unlock(w); } return change; @@ -374,6 +398,7 @@ static void dapm_set_path_status(struct snd_soc_dapm_widget *w, case snd_soc_dapm_mic: case snd_soc_dapm_spk: case snd_soc_dapm_line: + case snd_soc_dapm_dai_link: p->connect = 1; break; /* does affect routing - dynamically connected */ @@ -682,11 +707,51 @@ static int snd_soc_dapm_suspend_check(struct snd_soc_dapm_widget *widget) } } +/* add widget to list if it's not already in the list */ +static int dapm_list_add_widget(struct snd_soc_dapm_widget_list **list, + struct snd_soc_dapm_widget *w) +{ + struct snd_soc_dapm_widget_list *wlist; + int wlistsize, wlistentries, i; + + if (*list == NULL) + return -EINVAL; + + wlist = *list; + + /* is this widget already in the list */ + for (i = 0; i < wlist->num_widgets; i++) { + if (wlist->widgets[i] == w) + return 0; + } + + /* allocate some new space */ + wlistentries = wlist->num_widgets + 1; + wlistsize = sizeof(struct snd_soc_dapm_widget_list) + + wlistentries * sizeof(struct snd_soc_dapm_widget *); + *list = krealloc(wlist, wlistsize, GFP_KERNEL); + if (*list == NULL) { + dev_err(w->dapm->dev, "can't allocate widget list for %s\n", + w->name); + return -ENOMEM; + } + wlist = *list; + + /* insert the widget */ + dev_dbg(w->dapm->dev, "added %s in widget list pos %d\n", + w->name, wlist->num_widgets); + + wlist->widgets[wlist->num_widgets] = w; + wlist->num_widgets++; + return 1; +} + /* * Recursively check for a completed path to an active or physically connected * output widget. Returns number of complete paths. */ -static int is_connected_output_ep(struct snd_soc_dapm_widget *widget) +static int is_connected_output_ep(struct snd_soc_dapm_widget *widget, + struct snd_soc_dapm_widget_list **list) { struct snd_soc_dapm_path *path; int con = 0; @@ -742,9 +807,23 @@ static int is_connected_output_ep(struct snd_soc_dapm_widget *widget) if (path->walked) continue; + trace_snd_soc_dapm_output_path(widget, path); + if (path->sink && path->connect) { path->walked = 1; - con += is_connected_output_ep(path->sink); + + /* do we need to add this widget to the list ? */ + if (list) { + int err; + err = dapm_list_add_widget(list, path->sink); + if (err < 0) { + dev_err(widget->dapm->dev, "could not add widget %s\n", + widget->name); + return con; + } + } + + con += is_connected_output_ep(path->sink, list); } } @@ -757,7 +836,8 @@ static int is_connected_output_ep(struct snd_soc_dapm_widget *widget) * Recursively check for a completed path to an active or physically connected * input widget. Returns number of complete paths. */ -static int is_connected_input_ep(struct snd_soc_dapm_widget *widget) +static int is_connected_input_ep(struct snd_soc_dapm_widget *widget, + struct snd_soc_dapm_widget_list **list) { struct snd_soc_dapm_path *path; int con = 0; @@ -825,9 +905,23 @@ static int is_connected_input_ep(struct snd_soc_dapm_widget *widget) if (path->walked) continue; + trace_snd_soc_dapm_input_path(widget, path); + if (path->source && path->connect) { path->walked = 1; - con += is_connected_input_ep(path->source); + + /* do we need to add this widget to the list ? */ + if (list) { + int err; + err = dapm_list_add_widget(list, path->sink); + if (err < 0) { + dev_err(widget->dapm->dev, "could not add widget %s\n", + widget->name); + return con; + } + } + + con += is_connected_input_ep(path->source, list); } } @@ -836,6 +930,39 @@ static int is_connected_input_ep(struct snd_soc_dapm_widget *widget) return con; } +/** + * snd_soc_dapm_get_connected_widgets - query audio path and it's widgets. + * @dai: the soc DAI. + * @stream: stream direction. + * @list: list of active widgets for this stream. + * + * Queries DAPM graph as to whether an valid audio stream path exists for + * the initial stream specified by name. This takes into account + * current mixer and mux kcontrol settings. Creates list of valid widgets. + * + * Returns the number of valid paths or negative error. + */ +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; + int paths; + + mutex_lock_nested(&card->dapm_mutex, SND_SOC_DAPM_CLASS_RUNTIME); + dapm_reset(card); + + if (stream == SNDRV_PCM_STREAM_PLAYBACK) + paths = is_connected_output_ep(dai->playback_widget, list); + else + paths = is_connected_input_ep(dai->playback_widget, list); + + trace_snd_soc_dapm_connected(paths, stream); + dapm_clear_walk(&card->dapm); + mutex_unlock(&card->dapm_mutex); + + return paths; +} + /* * Handler for generic register modifier widget. */ @@ -849,7 +976,7 @@ int dapm_reg_event(struct snd_soc_dapm_widget *w, else val = w->off_val; - soc_widget_update_bits(w, -(w->reg + 1), + soc_widget_update_bits_locked(w, -(w->reg + 1), w->mask << w->shift, val << w->shift); return 0; @@ -863,9 +990,9 @@ int dapm_regulator_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { if (SND_SOC_DAPM_EVENT_ON(event)) - return regulator_enable(w->priv); + return regulator_enable(w->regulator); else - return regulator_disable_deferred(w->priv, w->shift); + return regulator_disable_deferred(w->regulator, w->shift); } EXPORT_SYMBOL_GPL(dapm_regulator_event); @@ -892,9 +1019,9 @@ static int dapm_generic_check_power(struct snd_soc_dapm_widget *w) DAPM_UPDATE_STAT(w, power_checks); - in = is_connected_input_ep(w); + in = is_connected_input_ep(w, NULL); dapm_clear_walk(w->dapm); - out = is_connected_output_ep(w); + out = is_connected_output_ep(w, NULL); dapm_clear_walk(w->dapm); return out != 0 && in != 0; } @@ -903,7 +1030,10 @@ static int dapm_dai_check_power(struct snd_soc_dapm_widget *w) { DAPM_UPDATE_STAT(w, power_checks); - return w->active; + if (w->active) + return w->active; + + return dapm_generic_check_power(w); } /* Check to see if an ADC has power */ @@ -914,7 +1044,7 @@ static int dapm_adc_check_power(struct snd_soc_dapm_widget *w) DAPM_UPDATE_STAT(w, power_checks); if (w->active) { - in = is_connected_input_ep(w); + in = is_connected_input_ep(w, NULL); dapm_clear_walk(w->dapm); return in != 0; } else { @@ -930,7 +1060,7 @@ static int dapm_dac_check_power(struct snd_soc_dapm_widget *w) DAPM_UPDATE_STAT(w, power_checks); if (w->active) { - out = is_connected_output_ep(w); + out = is_connected_output_ep(w, NULL); dapm_clear_walk(w->dapm); return out != 0; } else { @@ -1107,7 +1237,7 @@ static void dapm_seq_run_coalesced(struct snd_soc_dapm_context *dapm, "pop test : Applying 0x%x/0x%x to %x in %dms\n", value, mask, reg, card->pop_time); pop_wait(card->pop_time); - soc_widget_update_bits(w, reg, mask, value); + soc_widget_update_bits_locked(w, reg, mask, value); } list_for_each_entry(w, pending, power_list) { @@ -1237,7 +1367,7 @@ static void dapm_widget_update(struct snd_soc_dapm_context *dapm) w->name, ret); } - ret = snd_soc_update_bits(w->codec, update->reg, update->mask, + ret = soc_widget_update_bits_locked(w, update->reg, update->mask, update->val); if (ret < 0) pr_err("%s DAPM update failed: %d\n", w->name, ret); @@ -1421,12 +1551,10 @@ static int dapm_power_widgets(struct snd_soc_dapm_context *dapm, int event) trace_snd_soc_dapm_start(card); list_for_each_entry(d, &card->dapm_list, list) { - if (d->n_widgets || d->codec == NULL) { - if (d->idle_bias_off) - d->target_bias_level = SND_SOC_BIAS_OFF; - else - d->target_bias_level = SND_SOC_BIAS_STANDBY; - } + if (d->idle_bias_off) + d->target_bias_level = SND_SOC_BIAS_OFF; + else + d->target_bias_level = SND_SOC_BIAS_STANDBY; } dapm_reset(card); @@ -1471,32 +1599,6 @@ static int dapm_power_widgets(struct snd_soc_dapm_context *dapm, int event) } - /* If there are no DAPM widgets then try to figure out power from the - * event type. - */ - if (!dapm->n_widgets) { - switch (event) { - case SND_SOC_DAPM_STREAM_START: - case SND_SOC_DAPM_STREAM_RESUME: - dapm->target_bias_level = SND_SOC_BIAS_ON; - break; - case SND_SOC_DAPM_STREAM_STOP: - if (dapm->codec && dapm->codec->active) - dapm->target_bias_level = SND_SOC_BIAS_ON; - else - dapm->target_bias_level = SND_SOC_BIAS_STANDBY; - break; - case SND_SOC_DAPM_STREAM_SUSPEND: - dapm->target_bias_level = SND_SOC_BIAS_STANDBY; - break; - case SND_SOC_DAPM_STREAM_NOP: - dapm->target_bias_level = dapm->bias_level; - break; - default: - break; - } - } - /* Force all contexts in the card to the same bias state if * they're not ground referenced. */ @@ -1560,9 +1662,9 @@ static ssize_t dapm_widget_power_read_file(struct file *file, if (!buf) return -ENOMEM; - in = is_connected_input_ep(w); + in = is_connected_input_ep(w, NULL); dapm_clear_walk(w->dapm); - out = is_connected_output_ep(w); + out = is_connected_output_ep(w, NULL); dapm_clear_walk(w->dapm); ret = snprintf(buf, PAGE_SIZE, "%s: %s%s in %d out %d", @@ -1709,7 +1811,7 @@ static inline void dapm_debugfs_cleanup(struct snd_soc_dapm_context *dapm) #endif /* test and update the power status of a mux widget */ -int snd_soc_dapm_mux_update_power(struct snd_soc_dapm_widget *widget, +static int soc_dapm_mux_update_power(struct snd_soc_dapm_widget *widget, struct snd_kcontrol *kcontrol, int mux, struct soc_enum *e) { struct snd_soc_dapm_path *path; @@ -1746,12 +1848,26 @@ int snd_soc_dapm_mux_update_power(struct snd_soc_dapm_widget *widget, dapm_power_widgets(widget->dapm, SND_SOC_DAPM_STREAM_NOP); } - return 0; + return found; +} + +int snd_soc_dapm_mux_update_power(struct snd_soc_dapm_widget *widget, + struct snd_kcontrol *kcontrol, int mux, struct soc_enum *e) +{ + struct snd_soc_card *card = widget->dapm->card; + int ret; + + mutex_lock_nested(&card->dapm_mutex, SND_SOC_DAPM_CLASS_RUNTIME); + ret = soc_dapm_mux_update_power(widget, kcontrol, mux, e); + mutex_unlock(&card->dapm_mutex); + if (ret > 0) + soc_dpcm_runtime_update(widget); + return ret; } EXPORT_SYMBOL_GPL(snd_soc_dapm_mux_update_power); /* test and update the power status of a mixer or switch widget */ -int snd_soc_dapm_mixer_update_power(struct snd_soc_dapm_widget *widget, +static int soc_dapm_mixer_update_power(struct snd_soc_dapm_widget *widget, struct snd_kcontrol *kcontrol, int connect) { struct snd_soc_dapm_path *path; @@ -1778,7 +1894,21 @@ int snd_soc_dapm_mixer_update_power(struct snd_soc_dapm_widget *widget, dapm_power_widgets(widget->dapm, SND_SOC_DAPM_STREAM_NOP); } - return 0; + return found; +} + +int snd_soc_dapm_mixer_update_power(struct snd_soc_dapm_widget *widget, + struct snd_kcontrol *kcontrol, int connect) +{ + struct snd_soc_card *card = widget->dapm->card; + int ret; + + mutex_lock_nested(&card->dapm_mutex, SND_SOC_DAPM_CLASS_RUNTIME); + ret = soc_dapm_mixer_update_power(widget, kcontrol, connect); + mutex_unlock(&card->dapm_mutex); + if (ret > 0) + soc_dpcm_runtime_update(widget); + return ret; } EXPORT_SYMBOL_GPL(snd_soc_dapm_mixer_update_power); @@ -1939,6 +2069,8 @@ static int snd_soc_dapm_set_pin(struct snd_soc_dapm_context *dapm, */ int snd_soc_dapm_sync(struct snd_soc_dapm_context *dapm) { + int ret; + /* * Suppress early reports (eg, jacks syncing their state) to avoid * silly DAPM runs during card startup. @@ -1946,7 +2078,10 @@ int snd_soc_dapm_sync(struct snd_soc_dapm_context *dapm) if (!dapm->card || !dapm->card->instantiated) return 0; - return dapm_power_widgets(dapm, SND_SOC_DAPM_STREAM_NOP); + mutex_lock_nested(&dapm->card->dapm_mutex, SND_SOC_DAPM_CLASS_RUNTIME); + ret = dapm_power_widgets(dapm, SND_SOC_DAPM_STREAM_NOP); + mutex_unlock(&dapm->card->dapm_mutex); + return ret; } EXPORT_SYMBOL_GPL(snd_soc_dapm_sync); @@ -2055,6 +2190,7 @@ static int snd_soc_dapm_add_route(struct snd_soc_dapm_context *dapm, case snd_soc_dapm_aif_in: case snd_soc_dapm_aif_out: case snd_soc_dapm_dai: + case snd_soc_dapm_dai_link: list_add(&path->list, &dapm->card->paths); list_add(&path->list_sink, &wsink->sources); list_add(&path->list_source, &wsource->sinks); @@ -2110,19 +2246,21 @@ err: int snd_soc_dapm_add_routes(struct snd_soc_dapm_context *dapm, const struct snd_soc_dapm_route *route, int num) { - int i, ret; + int i, ret = 0; + mutex_lock_nested(&dapm->card->dapm_mutex, SND_SOC_DAPM_CLASS_INIT); for (i = 0; i < num; i++) { ret = snd_soc_dapm_add_route(dapm, route); if (ret < 0) { dev_err(dapm->dev, "Failed to add route %s->%s\n", route->source, route->sink); - return ret; + break; } route++; } + mutex_unlock(&dapm->card->dapm_mutex); - return 0; + return ret; } EXPORT_SYMBOL_GPL(snd_soc_dapm_add_routes); @@ -2193,12 +2331,14 @@ int snd_soc_dapm_weak_routes(struct snd_soc_dapm_context *dapm, int i, err; int ret = 0; + mutex_lock_nested(&dapm->card->dapm_mutex, SND_SOC_DAPM_CLASS_INIT); for (i = 0; i < num; i++) { err = snd_soc_dapm_weak_route(dapm, route); if (err) ret = err; route++; } + mutex_unlock(&dapm->card->dapm_mutex); return ret; } @@ -2217,6 +2357,8 @@ int snd_soc_dapm_new_widgets(struct snd_soc_dapm_context *dapm) struct snd_soc_dapm_widget *w; unsigned int val; + mutex_lock_nested(&dapm->card->dapm_mutex, SND_SOC_DAPM_CLASS_INIT); + list_for_each_entry(w, &dapm->card->widgets, list) { if (w->new) @@ -2226,8 +2368,10 @@ int snd_soc_dapm_new_widgets(struct snd_soc_dapm_context *dapm) w->kcontrols = kzalloc(w->num_kcontrols * sizeof(struct snd_kcontrol *), GFP_KERNEL); - if (!w->kcontrols) + if (!w->kcontrols) { + mutex_unlock(&dapm->card->dapm_mutex); return -ENOMEM; + } } switch(w->id) { @@ -2267,6 +2411,7 @@ int snd_soc_dapm_new_widgets(struct snd_soc_dapm_context *dapm) } dapm_power_widgets(dapm, SND_SOC_DAPM_STREAM_NOP); + mutex_unlock(&dapm->card->dapm_mutex); return 0; } EXPORT_SYMBOL_GPL(snd_soc_dapm_new_widgets); @@ -2326,6 +2471,7 @@ int snd_soc_dapm_put_volsw(struct snd_kcontrol *kcontrol, struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol); struct snd_soc_dapm_widget *widget = wlist->widgets[0]; struct snd_soc_codec *codec = widget->codec; + struct snd_soc_card *card = codec->card; struct soc_mixer_control *mc = (struct soc_mixer_control *)kcontrol->private_value; unsigned int reg = mc->reg; @@ -2352,7 +2498,7 @@ int snd_soc_dapm_put_volsw(struct snd_kcontrol *kcontrol, /* old connection must be powered down */ connect = invert ? 1 : 0; - mutex_lock(&codec->mutex); + mutex_lock_nested(&card->dapm_mutex, SND_SOC_DAPM_CLASS_RUNTIME); change = snd_soc_test_bits(widget->codec, reg, mask, val); if (change) { @@ -2368,13 +2514,13 @@ int snd_soc_dapm_put_volsw(struct snd_kcontrol *kcontrol, update.val = val; widget->dapm->update = &update; - snd_soc_dapm_mixer_update_power(widget, kcontrol, connect); + soc_dapm_mixer_update_power(widget, kcontrol, connect); widget->dapm->update = NULL; } } - mutex_unlock(&codec->mutex); + mutex_unlock(&card->dapm_mutex); return 0; } EXPORT_SYMBOL_GPL(snd_soc_dapm_put_volsw); @@ -2423,6 +2569,7 @@ int snd_soc_dapm_put_enum_double(struct snd_kcontrol *kcontrol, struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol); struct snd_soc_dapm_widget *widget = wlist->widgets[0]; struct snd_soc_codec *codec = widget->codec; + struct snd_soc_card *card = codec->card; struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; unsigned int val, mux, change; unsigned int mask, bitmask; @@ -2443,7 +2590,7 @@ int snd_soc_dapm_put_enum_double(struct snd_kcontrol *kcontrol, mask |= (bitmask - 1) << e->shift_r; } - mutex_lock(&codec->mutex); + mutex_lock_nested(&card->dapm_mutex, SND_SOC_DAPM_CLASS_RUNTIME); change = snd_soc_test_bits(widget->codec, e->reg, mask, val); if (change) { @@ -2459,13 +2606,13 @@ int snd_soc_dapm_put_enum_double(struct snd_kcontrol *kcontrol, update.val = val; widget->dapm->update = &update; - snd_soc_dapm_mux_update_power(widget, kcontrol, mux, e); + soc_dapm_mux_update_power(widget, kcontrol, mux, e); widget->dapm->update = NULL; } } - mutex_unlock(&codec->mutex); + mutex_unlock(&card->dapm_mutex); return change; } EXPORT_SYMBOL_GPL(snd_soc_dapm_put_enum_double); @@ -2502,6 +2649,7 @@ int snd_soc_dapm_put_enum_virt(struct snd_kcontrol *kcontrol, struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol); struct snd_soc_dapm_widget *widget = wlist->widgets[0]; struct snd_soc_codec *codec = widget->codec; + struct snd_soc_card *card = codec->card; struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; int change; @@ -2511,7 +2659,7 @@ int snd_soc_dapm_put_enum_virt(struct snd_kcontrol *kcontrol, if (ucontrol->value.enumerated.item[0] >= e->max) return -EINVAL; - mutex_lock(&codec->mutex); + mutex_lock_nested(&card->dapm_mutex, SND_SOC_DAPM_CLASS_RUNTIME); change = widget->value != ucontrol->value.enumerated.item[0]; if (change) { @@ -2520,11 +2668,11 @@ int snd_soc_dapm_put_enum_virt(struct snd_kcontrol *kcontrol, widget->value = ucontrol->value.enumerated.item[0]; - snd_soc_dapm_mux_update_power(widget, kcontrol, widget->value, e); + soc_dapm_mux_update_power(widget, kcontrol, widget->value, e); } } - mutex_unlock(&codec->mutex); + mutex_unlock(&card->dapm_mutex); return ret; } EXPORT_SYMBOL_GPL(snd_soc_dapm_put_enum_virt); @@ -2589,6 +2737,7 @@ int snd_soc_dapm_put_value_enum_double(struct snd_kcontrol *kcontrol, struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol); struct snd_soc_dapm_widget *widget = wlist->widgets[0]; struct snd_soc_codec *codec = widget->codec; + struct snd_soc_card *card = codec->card; struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; unsigned int val, mux, change; unsigned int mask; @@ -2607,7 +2756,7 @@ int snd_soc_dapm_put_value_enum_double(struct snd_kcontrol *kcontrol, mask |= e->mask << e->shift_r; } - mutex_lock(&codec->mutex); + mutex_lock_nested(&card->dapm_mutex, SND_SOC_DAPM_CLASS_RUNTIME); change = snd_soc_test_bits(widget->codec, e->reg, mask, val); if (change) { @@ -2623,13 +2772,13 @@ int snd_soc_dapm_put_value_enum_double(struct snd_kcontrol *kcontrol, update.val = val; widget->dapm->update = &update; - snd_soc_dapm_mux_update_power(widget, kcontrol, mux, e); + soc_dapm_mux_update_power(widget, kcontrol, mux, e); widget->dapm->update = NULL; } } - mutex_unlock(&codec->mutex); + mutex_unlock(&card->dapm_mutex); return change; } EXPORT_SYMBOL_GPL(snd_soc_dapm_put_value_enum_double); @@ -2666,12 +2815,12 @@ int snd_soc_dapm_get_pin_switch(struct snd_kcontrol *kcontrol, struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); const char *pin = (const char *)kcontrol->private_value; - mutex_lock(&card->mutex); + mutex_lock_nested(&card->dapm_mutex, SND_SOC_DAPM_CLASS_RUNTIME); ucontrol->value.integer.value[0] = snd_soc_dapm_get_pin_status(&card->dapm, pin); - mutex_unlock(&card->mutex); + mutex_unlock(&card->dapm_mutex); return 0; } @@ -2689,17 +2838,16 @@ int snd_soc_dapm_put_pin_switch(struct snd_kcontrol *kcontrol, struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); const char *pin = (const char *)kcontrol->private_value; - mutex_lock(&card->mutex); + mutex_lock_nested(&card->dapm_mutex, SND_SOC_DAPM_CLASS_RUNTIME); if (ucontrol->value.integer.value[0]) snd_soc_dapm_enable_pin(&card->dapm, pin); else snd_soc_dapm_disable_pin(&card->dapm, pin); - snd_soc_dapm_sync(&card->dapm); - - mutex_unlock(&card->mutex); + mutex_unlock(&card->dapm_mutex); + snd_soc_dapm_sync(&card->dapm); return 0; } EXPORT_SYMBOL_GPL(snd_soc_dapm_put_pin_switch); @@ -2717,9 +2865,9 @@ snd_soc_dapm_new_control(struct snd_soc_dapm_context *dapm, switch (w->id) { case snd_soc_dapm_regulator_supply: - w->priv = devm_regulator_get(dapm->dev, w->name); - if (IS_ERR(w->priv)) { - ret = PTR_ERR(w->priv); + w->regulator = devm_regulator_get(dapm->dev, w->name); + if (IS_ERR(w->regulator)) { + ret = PTR_ERR(w->regulator); dev_err(dapm->dev, "Failed to request %s: %d\n", w->name, ret); return NULL; @@ -2771,6 +2919,7 @@ snd_soc_dapm_new_control(struct snd_soc_dapm_context *dapm, case snd_soc_dapm_hp: case snd_soc_dapm_mic: case snd_soc_dapm_line: + case snd_soc_dapm_dai_link: w->power_check = dapm_generic_check_power; break; case snd_soc_dapm_supply: @@ -2816,21 +2965,177 @@ int snd_soc_dapm_new_controls(struct snd_soc_dapm_context *dapm, { struct snd_soc_dapm_widget *w; int i; + int ret = 0; + mutex_lock_nested(&dapm->card->dapm_mutex, SND_SOC_DAPM_CLASS_INIT); for (i = 0; i < num; i++) { w = snd_soc_dapm_new_control(dapm, widget); if (!w) { dev_err(dapm->dev, "ASoC: Failed to create DAPM control %s\n", widget->name); - return -ENOMEM; + ret = -ENOMEM; + break; } widget++; } - return 0; + mutex_unlock(&dapm->card->dapm_mutex); + return ret; } EXPORT_SYMBOL_GPL(snd_soc_dapm_new_controls); +static int snd_soc_dai_link_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_dapm_path *source_p, *sink_p; + struct snd_soc_dai *source, *sink; + const struct snd_soc_pcm_stream *config = w->params; + struct snd_pcm_substream substream; + struct snd_pcm_hw_params *params = NULL; + u64 fmt; + int ret; + + BUG_ON(!config); + BUG_ON(list_empty(&w->sources) || list_empty(&w->sinks)); + + /* We only support a single source and sink, pick the first */ + source_p = list_first_entry(&w->sources, struct snd_soc_dapm_path, + list_sink); + sink_p = list_first_entry(&w->sinks, struct snd_soc_dapm_path, + list_source); + + BUG_ON(!source_p || !sink_p); + BUG_ON(!sink_p->source || !source_p->sink); + BUG_ON(!source_p->source || !sink_p->sink); + + source = source_p->source->priv; + sink = sink_p->sink->priv; + + /* Be a little careful as we don't want to overflow the mask array */ + if (config->formats) { + fmt = ffs(config->formats) - 1; + } else { + dev_warn(w->dapm->dev, "Invalid format %llx specified\n", + config->formats); + fmt = 0; + } + + /* Currently very limited parameter selection */ + params = kzalloc(sizeof(*params), GFP_KERNEL); + if (!params) { + ret = -ENOMEM; + goto out; + } + snd_mask_set(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), fmt); + + hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE)->min = + config->rate_min; + hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE)->max = + config->rate_max; + + hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS)->min + = config->channels_min; + hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS)->max + = config->channels_max; + + memset(&substream, 0, sizeof(substream)); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + if (source->driver->ops && source->driver->ops->hw_params) { + substream.stream = SNDRV_PCM_STREAM_CAPTURE; + ret = source->driver->ops->hw_params(&substream, + params, source); + if (ret != 0) { + dev_err(source->dev, + "hw_params() failed: %d\n", ret); + goto out; + } + } + + if (sink->driver->ops && sink->driver->ops->hw_params) { + substream.stream = SNDRV_PCM_STREAM_PLAYBACK; + ret = sink->driver->ops->hw_params(&substream, params, + sink); + if (ret != 0) { + dev_err(sink->dev, + "hw_params() failed: %d\n", ret); + goto out; + } + } + break; + + case SND_SOC_DAPM_POST_PMU: + ret = snd_soc_dai_digital_mute(sink, 0); + if (ret != 0 && ret != -ENOTSUPP) + dev_warn(sink->dev, "Failed to unmute: %d\n", ret); + ret = 0; + break; + + case SND_SOC_DAPM_PRE_PMD: + ret = snd_soc_dai_digital_mute(sink, 1); + if (ret != 0 && ret != -ENOTSUPP) + dev_warn(sink->dev, "Failed to mute: %d\n", ret); + ret = 0; + break; + + default: + BUG(); + return -EINVAL; + } + +out: + kfree(params); + return ret; +} + +int snd_soc_dapm_new_pcm(struct snd_soc_card *card, + const struct snd_soc_pcm_stream *params, + struct snd_soc_dapm_widget *source, + struct snd_soc_dapm_widget *sink) +{ + struct snd_soc_dapm_route routes[2]; + struct snd_soc_dapm_widget template; + struct snd_soc_dapm_widget *w; + size_t len; + char *link_name; + + len = strlen(source->name) + strlen(sink->name) + 2; + link_name = devm_kzalloc(card->dev, len, GFP_KERNEL); + if (!link_name) + return -ENOMEM; + snprintf(link_name, len, "%s-%s", source->name, sink->name); + + memset(&template, 0, sizeof(template)); + template.reg = SND_SOC_NOPM; + template.id = snd_soc_dapm_dai_link; + template.name = link_name; + template.event = snd_soc_dai_link_event; + template.event_flags = SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_PRE_PMD; + + dev_dbg(card->dev, "adding %s widget\n", link_name); + + w = snd_soc_dapm_new_control(&card->dapm, &template); + if (!w) { + dev_err(card->dev, "Failed to create %s widget\n", + link_name); + return -ENOMEM; + } + + w->params = params; + + memset(&routes, 0, sizeof(routes)); + + routes[0].source = source->name; + routes[0].sink = link_name; + routes[1].source = link_name; + routes[1].sink = sink->name; + + return snd_soc_dapm_add_routes(&card->dapm, routes, + ARRAY_SIZE(routes)); +} + int snd_soc_dapm_new_dai_widgets(struct snd_soc_dapm_context *dapm, struct snd_soc_dai *dai) { @@ -2934,37 +3239,61 @@ int snd_soc_dapm_link_dai_widgets(struct snd_soc_card *card) return 0; } -static void soc_dapm_stream_event(struct snd_soc_dapm_context *dapm, - int stream, struct snd_soc_dai *dai, - int event) +static void soc_dapm_stream_event(struct snd_soc_pcm_runtime *rtd, int stream, + int event) { - struct snd_soc_dapm_widget *w; - if (stream == SNDRV_PCM_STREAM_PLAYBACK) - w = dai->playback_widget; - else - w = dai->capture_widget; + struct snd_soc_dapm_widget *w_cpu, *w_codec; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_dai *codec_dai = rtd->codec_dai; - if (!w) - return; + if (stream == SNDRV_PCM_STREAM_PLAYBACK) { + w_cpu = cpu_dai->playback_widget; + w_codec = codec_dai->playback_widget; + } else { + w_cpu = cpu_dai->capture_widget; + w_codec = codec_dai->capture_widget; + } - dapm_mark_dirty(w, "stream event"); + if (w_cpu) { - switch (event) { - case SND_SOC_DAPM_STREAM_START: - w->active = 1; - break; - case SND_SOC_DAPM_STREAM_STOP: - w->active = 0; - break; - case SND_SOC_DAPM_STREAM_SUSPEND: - case SND_SOC_DAPM_STREAM_RESUME: - case SND_SOC_DAPM_STREAM_PAUSE_PUSH: - case SND_SOC_DAPM_STREAM_PAUSE_RELEASE: - break; + dapm_mark_dirty(w_cpu, "stream event"); + + switch (event) { + case SND_SOC_DAPM_STREAM_START: + w_cpu->active = 1; + break; + case SND_SOC_DAPM_STREAM_STOP: + w_cpu->active = 0; + break; + case SND_SOC_DAPM_STREAM_SUSPEND: + case SND_SOC_DAPM_STREAM_RESUME: + case SND_SOC_DAPM_STREAM_PAUSE_PUSH: + case SND_SOC_DAPM_STREAM_PAUSE_RELEASE: + break; + } + } + + if (w_codec) { + + dapm_mark_dirty(w_codec, "stream event"); + + switch (event) { + case SND_SOC_DAPM_STREAM_START: + w_codec->active = 1; + break; + case SND_SOC_DAPM_STREAM_STOP: + w_codec->active = 0; + break; + case SND_SOC_DAPM_STREAM_SUSPEND: + case SND_SOC_DAPM_STREAM_RESUME: + case SND_SOC_DAPM_STREAM_PAUSE_PUSH: + case SND_SOC_DAPM_STREAM_PAUSE_RELEASE: + break; + } } - dapm_power_widgets(dapm, event); + dapm_power_widgets(&rtd->card->dapm, event); } /** @@ -2978,15 +3307,14 @@ static void soc_dapm_stream_event(struct snd_soc_dapm_context *dapm, * * Returns 0 for success else error. */ -int snd_soc_dapm_stream_event(struct snd_soc_pcm_runtime *rtd, int stream, - struct snd_soc_dai *dai, int event) +void snd_soc_dapm_stream_event(struct snd_soc_pcm_runtime *rtd, int stream, + int event) { - struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_card *card = rtd->card; - mutex_lock(&codec->mutex); - soc_dapm_stream_event(&codec->dapm, stream, dai, event); - mutex_unlock(&codec->mutex); - return 0; + mutex_lock_nested(&card->dapm_mutex, SND_SOC_DAPM_CLASS_RUNTIME); + soc_dapm_stream_event(rtd, stream, event); + mutex_unlock(&card->dapm_mutex); } /** diff --git a/sound/soc/soc-jack.c b/sound/soc/soc-jack.c index ee4353f843e..7f8b3b7428b 100644 --- a/sound/soc/soc-jack.c +++ b/sound/soc/soc-jack.c @@ -36,6 +36,7 @@ int snd_soc_jack_new(struct snd_soc_codec *codec, const char *id, int type, struct snd_soc_jack *jack) { + mutex_init(&jack->mutex); jack->codec = codec; INIT_LIST_HEAD(&jack->pins); INIT_LIST_HEAD(&jack->jack_zones); @@ -75,7 +76,7 @@ void snd_soc_jack_report(struct snd_soc_jack *jack, int status, int mask) codec = jack->codec; dapm = &codec->dapm; - mutex_lock(&codec->mutex); + mutex_lock(&jack->mutex); oldstatus = jack->status; @@ -109,7 +110,7 @@ void snd_soc_jack_report(struct snd_soc_jack *jack, int status, int mask) snd_jack_report(jack->jack, jack->status); out: - mutex_unlock(&codec->mutex); + mutex_unlock(&jack->mutex); } EXPORT_SYMBOL_GPL(snd_soc_jack_report); diff --git a/sound/soc/soc-pcm.c b/sound/soc/soc-pcm.c index 0ad8dcacd2f..bedd1717a37 100644 --- a/sound/soc/soc-pcm.c +++ b/sound/soc/soc-pcm.c @@ -22,12 +22,38 @@ #include <linux/pm_runtime.h> #include <linux/slab.h> #include <linux/workqueue.h> +#include <linux/export.h> +#include <linux/debugfs.h> #include <sound/core.h> #include <sound/pcm.h> #include <sound/pcm_params.h> #include <sound/soc.h> +#include <sound/soc-dpcm.h> #include <sound/initval.h> +#define DPCM_MAX_BE_USERS 8 + +/* DPCM stream event, send event to FE and all active BEs. */ +static int dpcm_dapm_stream_event(struct snd_soc_pcm_runtime *fe, int dir, + int event) +{ + struct snd_soc_dpcm *dpcm; + + list_for_each_entry(dpcm, &fe->dpcm[dir].be_clients, list_be) { + + struct snd_soc_pcm_runtime *be = dpcm->be; + + dev_dbg(be->dev, "pm: BE %s event %d dir %d\n", + be->dai_link->name, event, dir); + + snd_soc_dapm_stream_event(be, dir, event); + } + + snd_soc_dapm_stream_event(fe, dir, event); + + return 0; +} + static int soc_pcm_apply_symmetry(struct snd_pcm_substream *substream, struct snd_soc_dai *soc_dai) { @@ -156,6 +182,10 @@ static int soc_pcm_open(struct snd_pcm_substream *substream) } } + /* Dynamic PCM DAI links compat checks use dynamic capabilities */ + if (rtd->dai_link->dynamic || rtd->dai_link->no_pcm) + goto dynamic; + /* Check that the codec and cpu DAIs are compatible */ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { runtime->hw.rate_min = @@ -248,6 +278,7 @@ static int soc_pcm_open(struct snd_pcm_substream *substream) pr_debug("asoc: min rate %d max rate %d\n", runtime->hw.rate_min, runtime->hw.rate_max); +dynamic: if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { cpu_dai->playback_active++; codec_dai->playback_active++; @@ -308,7 +339,7 @@ static void close_delayed_work(struct work_struct *work) if (codec_dai->pop_wait == 1) { codec_dai->pop_wait = 0; snd_soc_dapm_stream_event(rtd, SNDRV_PCM_STREAM_PLAYBACK, - codec_dai, SND_SOC_DAPM_STREAM_STOP); + SND_SOC_DAPM_STREAM_STOP); } mutex_unlock(&rtd->pcm_mutex); @@ -373,7 +404,6 @@ static int soc_pcm_close(struct snd_pcm_substream *substream) /* powered down playback stream now */ snd_soc_dapm_stream_event(rtd, SNDRV_PCM_STREAM_PLAYBACK, - codec_dai, SND_SOC_DAPM_STREAM_STOP); } else { /* start delayed pop wq here for playback streams */ @@ -384,7 +414,7 @@ static int soc_pcm_close(struct snd_pcm_substream *substream) } else { /* capture streams can be powered down now */ snd_soc_dapm_stream_event(rtd, SNDRV_PCM_STREAM_CAPTURE, - codec_dai, SND_SOC_DAPM_STREAM_STOP); + SND_SOC_DAPM_STREAM_STOP); } mutex_unlock(&rtd->pcm_mutex); @@ -453,8 +483,8 @@ static int soc_pcm_prepare(struct snd_pcm_substream *substream) cancel_delayed_work(&rtd->delayed_work); } - snd_soc_dapm_stream_event(rtd, substream->stream, codec_dai, - SND_SOC_DAPM_STREAM_START); + snd_soc_dapm_stream_event(rtd, substream->stream, + SND_SOC_DAPM_STREAM_START); snd_soc_dai_digital_mute(codec_dai, 0); @@ -602,6 +632,34 @@ static int soc_pcm_trigger(struct snd_pcm_substream *substream, int cmd) return 0; } +static int soc_pcm_bespoke_trigger(struct snd_pcm_substream *substream, + int cmd) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_platform *platform = rtd->platform; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + int ret; + + if (codec_dai->driver->ops->bespoke_trigger) { + ret = codec_dai->driver->ops->bespoke_trigger(substream, cmd, codec_dai); + if (ret < 0) + return ret; + } + + if (platform->driver->bespoke_trigger) { + ret = platform->driver->bespoke_trigger(substream, cmd); + if (ret < 0) + return ret; + } + + if (cpu_dai->driver->ops->bespoke_trigger) { + ret = cpu_dai->driver->ops->bespoke_trigger(substream, cmd, cpu_dai); + if (ret < 0) + return ret; + } + return 0; +} /* * soc level wrapper for pointer callback * If cpu_dai, codec_dai, platform driver has the delay callback, than @@ -634,6 +692,1308 @@ static snd_pcm_uframes_t soc_pcm_pointer(struct snd_pcm_substream *substream) return offset; } +/* connect a FE and BE */ +static int dpcm_be_connect(struct snd_soc_pcm_runtime *fe, + struct snd_soc_pcm_runtime *be, int stream) +{ + struct snd_soc_dpcm *dpcm; + + /* only add new dpcms */ + list_for_each_entry(dpcm, &fe->dpcm[stream].be_clients, list_be) { + if (dpcm->be == be && dpcm->fe == fe) + return 0; + } + + dpcm = kzalloc(sizeof(struct snd_soc_dpcm), GFP_KERNEL); + if (!dpcm) + return -ENOMEM; + + dpcm->be = be; + dpcm->fe = fe; + be->dpcm[stream].runtime = fe->dpcm[stream].runtime; + dpcm->state = SND_SOC_DPCM_LINK_STATE_NEW; + list_add(&dpcm->list_be, &fe->dpcm[stream].be_clients); + list_add(&dpcm->list_fe, &be->dpcm[stream].fe_clients); + + dev_dbg(fe->dev, " connected new DPCM %s path %s %s %s\n", + stream ? "capture" : "playback", fe->dai_link->name, + stream ? "<-" : "->", be->dai_link->name); + +#ifdef CONFIG_DEBUG_FS + dpcm->debugfs_state = debugfs_create_u32(be->dai_link->name, 0644, + fe->debugfs_dpcm_root, &dpcm->state); +#endif + return 1; +} + +/* reparent a BE onto another FE */ +static void dpcm_be_reparent(struct snd_soc_pcm_runtime *fe, + struct snd_soc_pcm_runtime *be, int stream) +{ + struct snd_soc_dpcm *dpcm; + struct snd_pcm_substream *fe_substream, *be_substream; + + /* reparent if BE is connected to other FEs */ + if (!be->dpcm[stream].users) + return; + + be_substream = snd_soc_dpcm_get_substream(be, stream); + + list_for_each_entry(dpcm, &be->dpcm[stream].fe_clients, list_fe) { + if (dpcm->fe == fe) + continue; + + dev_dbg(fe->dev, " reparent %s path %s %s %s\n", + stream ? "capture" : "playback", + dpcm->fe->dai_link->name, + stream ? "<-" : "->", dpcm->be->dai_link->name); + + fe_substream = snd_soc_dpcm_get_substream(dpcm->fe, stream); + be_substream->runtime = fe_substream->runtime; + break; + } +} + +/* disconnect a BE and FE */ +static void dpcm_be_disconnect(struct snd_soc_pcm_runtime *fe, int stream) +{ + struct snd_soc_dpcm *dpcm, *d; + + list_for_each_entry_safe(dpcm, d, &fe->dpcm[stream].be_clients, list_be) { + dev_dbg(fe->dev, "BE %s disconnect check for %s\n", + stream ? "capture" : "playback", + dpcm->be->dai_link->name); + + if (dpcm->state != SND_SOC_DPCM_LINK_STATE_FREE) + continue; + + dev_dbg(fe->dev, " freed DSP %s path %s %s %s\n", + stream ? "capture" : "playback", fe->dai_link->name, + stream ? "<-" : "->", dpcm->be->dai_link->name); + + /* BEs still alive need new FE */ + dpcm_be_reparent(fe, dpcm->be, stream); + +#ifdef CONFIG_DEBUG_FS + debugfs_remove(dpcm->debugfs_state); +#endif + list_del(&dpcm->list_be); + list_del(&dpcm->list_fe); + kfree(dpcm); + } +} + +/* get BE for DAI widget and stream */ +static struct snd_soc_pcm_runtime *dpcm_get_be(struct snd_soc_card *card, + struct snd_soc_dapm_widget *widget, int stream) +{ + struct snd_soc_pcm_runtime *be; + int i; + + if (stream == SNDRV_PCM_STREAM_PLAYBACK) { + for (i = 0; i < card->num_links; i++) { + be = &card->rtd[i]; + + if (be->cpu_dai->playback_widget == widget || + be->codec_dai->playback_widget == widget) + return be; + } + } else { + + for (i = 0; i < card->num_links; i++) { + be = &card->rtd[i]; + + if (be->cpu_dai->capture_widget == widget || + be->codec_dai->capture_widget == widget) + return be; + } + } + + dev_err(card->dev, "can't get %s BE for %s\n", + stream ? "capture" : "playback", widget->name); + return NULL; +} + +static inline struct snd_soc_dapm_widget * + rtd_get_cpu_widget(struct snd_soc_pcm_runtime *rtd, int stream) +{ + if (stream == SNDRV_PCM_STREAM_PLAYBACK) + return rtd->cpu_dai->playback_widget; + else + return rtd->cpu_dai->capture_widget; +} + +static inline struct snd_soc_dapm_widget * + rtd_get_codec_widget(struct snd_soc_pcm_runtime *rtd, int stream) +{ + if (stream == SNDRV_PCM_STREAM_PLAYBACK) + return rtd->codec_dai->playback_widget; + else + return rtd->codec_dai->capture_widget; +} + +static int widget_in_list(struct snd_soc_dapm_widget_list *list, + struct snd_soc_dapm_widget *widget) +{ + int i; + + for (i = 0; i < list->num_widgets; i++) { + if (widget == list->widgets[i]) + return 1; + } + + return 0; +} + +static int dpcm_path_get(struct snd_soc_pcm_runtime *fe, + int stream, struct snd_soc_dapm_widget_list **list_) +{ + struct snd_soc_dai *cpu_dai = fe->cpu_dai; + struct snd_soc_dapm_widget_list *list; + int paths; + + list = kzalloc(sizeof(struct snd_soc_dapm_widget_list) + + sizeof(struct snd_soc_dapm_widget *), GFP_KERNEL); + if (list == NULL) + return -ENOMEM; + + /* get number of valid DAI paths and their widgets */ + paths = snd_soc_dapm_dai_get_connected_widgets(cpu_dai, stream, &list); + + dev_dbg(fe->dev, "found %d audio %s paths\n", paths, + stream ? "capture" : "playback"); + + *list_ = list; + return paths; +} + +static inline void dpcm_path_put(struct snd_soc_dapm_widget_list **list) +{ + kfree(*list); +} + +static int dpcm_prune_paths(struct snd_soc_pcm_runtime *fe, int stream, + struct snd_soc_dapm_widget_list **list_) +{ + struct snd_soc_dpcm *dpcm; + struct snd_soc_dapm_widget_list *list = *list_; + struct snd_soc_dapm_widget *widget; + int prune = 0; + + /* Destroy any old FE <--> BE connections */ + list_for_each_entry(dpcm, &fe->dpcm[stream].be_clients, list_be) { + + /* is there a valid CPU DAI widget for this BE */ + widget = rtd_get_cpu_widget(dpcm->be, stream); + + /* prune the BE if it's no longer in our active list */ + if (widget && widget_in_list(list, widget)) + continue; + + /* is there a valid CODEC DAI widget for this BE */ + widget = rtd_get_codec_widget(dpcm->be, stream); + + /* prune the BE if it's no longer in our active list */ + if (widget && widget_in_list(list, widget)) + continue; + + dev_dbg(fe->dev, "pruning %s BE %s for %s\n", + stream ? "capture" : "playback", + dpcm->be->dai_link->name, fe->dai_link->name); + dpcm->state = SND_SOC_DPCM_LINK_STATE_FREE; + dpcm->be->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_BE; + prune++; + } + + dev_dbg(fe->dev, "found %d old BE paths for pruning\n", prune); + return prune; +} + +static int dpcm_add_paths(struct snd_soc_pcm_runtime *fe, int stream, + struct snd_soc_dapm_widget_list **list_) +{ + struct snd_soc_card *card = fe->card; + struct snd_soc_dapm_widget_list *list = *list_; + struct snd_soc_pcm_runtime *be; + int i, new = 0, err; + + /* Create any new FE <--> BE connections */ + for (i = 0; i < list->num_widgets; i++) { + + if (list->widgets[i]->id != snd_soc_dapm_dai) + continue; + + /* is there a valid BE rtd for this widget */ + be = dpcm_get_be(card, list->widgets[i], stream); + if (!be) { + dev_err(fe->dev, "no BE found for %s\n", + list->widgets[i]->name); + continue; + } + + /* make sure BE is a real BE */ + if (!be->dai_link->no_pcm) + continue; + + /* don't connect if FE is not running */ + if (!fe->dpcm[stream].runtime) + continue; + + /* newly connected FE and BE */ + err = dpcm_be_connect(fe, be, stream); + if (err < 0) { + dev_err(fe->dev, "can't connect %s\n", + list->widgets[i]->name); + break; + } else if (err == 0) /* already connected */ + continue; + + /* new */ + be->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_BE; + new++; + } + + dev_dbg(fe->dev, "found %d new BE paths\n", new); + return new; +} + +/* + * Find the corresponding BE DAIs that source or sink audio to this + * FE substream. + */ +static int dpcm_process_paths(struct snd_soc_pcm_runtime *fe, + int stream, struct snd_soc_dapm_widget_list **list, int new) +{ + if (new) + return dpcm_add_paths(fe, stream, list); + else + return dpcm_prune_paths(fe, stream, list); +} + +static void dpcm_clear_pending_state(struct snd_soc_pcm_runtime *fe, int stream) +{ + struct snd_soc_dpcm *dpcm; + + list_for_each_entry(dpcm, &fe->dpcm[stream].be_clients, list_be) + dpcm->be->dpcm[stream].runtime_update = + SND_SOC_DPCM_UPDATE_NO; +} + +static void dpcm_be_dai_startup_unwind(struct snd_soc_pcm_runtime *fe, + int stream) +{ + struct snd_soc_dpcm *dpcm; + + /* disable any enabled and non active backends */ + list_for_each_entry(dpcm, &fe->dpcm[stream].be_clients, list_be) { + + struct snd_soc_pcm_runtime *be = dpcm->be; + struct snd_pcm_substream *be_substream = + snd_soc_dpcm_get_substream(be, stream); + + if (be->dpcm[stream].users == 0) + dev_err(be->dev, "no users %s at close - state %d\n", + stream ? "capture" : "playback", + be->dpcm[stream].state); + + if (--be->dpcm[stream].users != 0) + continue; + + if (be->dpcm[stream].state != SND_SOC_DPCM_STATE_OPEN) + continue; + + soc_pcm_close(be_substream); + be_substream->runtime = NULL; + be->dpcm[stream].state = SND_SOC_DPCM_STATE_CLOSE; + } +} + +static int dpcm_be_dai_startup(struct snd_soc_pcm_runtime *fe, int stream) +{ + struct snd_soc_dpcm *dpcm; + int err, count = 0; + + /* only startup BE DAIs that are either sinks or sources to this FE DAI */ + list_for_each_entry(dpcm, &fe->dpcm[stream].be_clients, list_be) { + + struct snd_soc_pcm_runtime *be = dpcm->be; + struct snd_pcm_substream *be_substream = + snd_soc_dpcm_get_substream(be, stream); + + /* is this op for this BE ? */ + if (!snd_soc_dpcm_be_can_update(fe, be, stream)) + continue; + + /* first time the dpcm is open ? */ + if (be->dpcm[stream].users == DPCM_MAX_BE_USERS) + dev_err(be->dev, "too many users %s at open %d\n", + stream ? "capture" : "playback", + be->dpcm[stream].state); + + if (be->dpcm[stream].users++ != 0) + continue; + + if ((be->dpcm[stream].state != SND_SOC_DPCM_STATE_NEW) && + (be->dpcm[stream].state != SND_SOC_DPCM_STATE_CLOSE)) + continue; + + dev_dbg(be->dev, "dpcm: open BE %s\n", be->dai_link->name); + + be_substream->runtime = be->dpcm[stream].runtime; + err = soc_pcm_open(be_substream); + if (err < 0) { + dev_err(be->dev, "BE open failed %d\n", err); + be->dpcm[stream].users--; + if (be->dpcm[stream].users < 0) + dev_err(be->dev, "no users %s at unwind %d\n", + stream ? "capture" : "playback", + be->dpcm[stream].state); + + be->dpcm[stream].state = SND_SOC_DPCM_STATE_CLOSE; + goto unwind; + } + + be->dpcm[stream].state = SND_SOC_DPCM_STATE_OPEN; + count++; + } + + return count; + +unwind: + /* disable any enabled and non active backends */ + list_for_each_entry_continue_reverse(dpcm, &fe->dpcm[stream].be_clients, list_be) { + struct snd_soc_pcm_runtime *be = dpcm->be; + struct snd_pcm_substream *be_substream = + snd_soc_dpcm_get_substream(be, stream); + + if (!snd_soc_dpcm_be_can_update(fe, be, stream)) + continue; + + if (be->dpcm[stream].users == 0) + dev_err(be->dev, "no users %s at close %d\n", + stream ? "capture" : "playback", + be->dpcm[stream].state); + + if (--be->dpcm[stream].users != 0) + continue; + + if (be->dpcm[stream].state != SND_SOC_DPCM_STATE_OPEN) + continue; + + soc_pcm_close(be_substream); + be_substream->runtime = NULL; + be->dpcm[stream].state = SND_SOC_DPCM_STATE_CLOSE; + } + + return err; +} + +static void dpcm_set_fe_runtime(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_dai_driver *cpu_dai_drv = cpu_dai->driver; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + runtime->hw.rate_min = cpu_dai_drv->playback.rate_min; + runtime->hw.rate_max = cpu_dai_drv->playback.rate_max; + runtime->hw.channels_min = cpu_dai_drv->playback.channels_min; + runtime->hw.channels_max = cpu_dai_drv->playback.channels_max; + runtime->hw.formats &= cpu_dai_drv->playback.formats; + runtime->hw.rates = cpu_dai_drv->playback.rates; + } else { + runtime->hw.rate_min = cpu_dai_drv->capture.rate_min; + runtime->hw.rate_max = cpu_dai_drv->capture.rate_max; + runtime->hw.channels_min = cpu_dai_drv->capture.channels_min; + runtime->hw.channels_max = cpu_dai_drv->capture.channels_max; + runtime->hw.formats &= cpu_dai_drv->capture.formats; + runtime->hw.rates = cpu_dai_drv->capture.rates; + } +} + +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; + + ret = dpcm_be_dai_startup(fe, fe_substream->stream); + if (ret < 0) { + dev_err(fe->dev,"dpcm: failed to start some BEs %d\n", ret); + goto be_err; + } + + dev_dbg(fe->dev, "dpcm: open FE %s\n", fe->dai_link->name); + + /* start the DAI frontend */ + ret = soc_pcm_open(fe_substream); + if (ret < 0) { + dev_err(fe->dev,"dpcm: failed to start FE %d\n", ret); + goto unwind; + } + + fe->dpcm[stream].state = SND_SOC_DPCM_STATE_OPEN; + + dpcm_set_fe_runtime(fe_substream); + snd_pcm_limit_hw_rates(runtime); + + fe->dpcm[stream].runtime_update = 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; + return ret; +} + +static int dpcm_be_dai_shutdown(struct snd_soc_pcm_runtime *fe, int stream) +{ + struct snd_soc_dpcm *dpcm; + + /* only shutdown BEs that are either sinks or sources to this FE DAI */ + list_for_each_entry(dpcm, &fe->dpcm[stream].be_clients, list_be) { + + struct snd_soc_pcm_runtime *be = dpcm->be; + struct snd_pcm_substream *be_substream = + snd_soc_dpcm_get_substream(be, stream); + + /* is this op for this BE ? */ + if (!snd_soc_dpcm_be_can_update(fe, be, stream)) + continue; + + if (be->dpcm[stream].users == 0) + dev_err(be->dev, "no users %s at close - state %d\n", + stream ? "capture" : "playback", + be->dpcm[stream].state); + + if (--be->dpcm[stream].users != 0) + continue; + + if ((be->dpcm[stream].state != SND_SOC_DPCM_STATE_HW_FREE) && + (be->dpcm[stream].state != SND_SOC_DPCM_STATE_OPEN)) + continue; + + dev_dbg(be->dev, "dpcm: close BE %s\n", + dpcm->fe->dai_link->name); + + soc_pcm_close(be_substream); + be_substream->runtime = NULL; + + be->dpcm[stream].state = SND_SOC_DPCM_STATE_CLOSE; + } + return 0; +} + +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; + + /* shutdown the BEs */ + dpcm_be_dai_shutdown(fe, substream->stream); + + dev_dbg(fe->dev, "dpcm: close FE %s\n", fe->dai_link->name); + + /* now shutdown the frontend */ + soc_pcm_close(substream); + + /* run the stream event for each BE */ + 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; + return 0; +} + +static int dpcm_be_dai_hw_free(struct snd_soc_pcm_runtime *fe, int stream) +{ + struct snd_soc_dpcm *dpcm; + + /* only hw_params backends that are either sinks or sources + * to this frontend DAI */ + list_for_each_entry(dpcm, &fe->dpcm[stream].be_clients, list_be) { + + struct snd_soc_pcm_runtime *be = dpcm->be; + struct snd_pcm_substream *be_substream = + snd_soc_dpcm_get_substream(be, stream); + + /* is this op for this BE ? */ + if (!snd_soc_dpcm_be_can_update(fe, be, stream)) + continue; + + /* only free hw when no longer used - check all FEs */ + if (!snd_soc_dpcm_can_be_free_stop(fe, be, stream)) + 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) && + (be->dpcm[stream].state != SND_SOC_DPCM_STATE_STOP)) + continue; + + dev_dbg(be->dev, "dpcm: hw_free BE %s\n", + dpcm->fe->dai_link->name); + + soc_pcm_hw_free(be_substream); + + be->dpcm[stream].state = SND_SOC_DPCM_STATE_HW_FREE; + } + + return 0; +} + +static int dpcm_fe_dai_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *fe = substream->private_data; + 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; + + dev_dbg(fe->dev, "dpcm: hw_free FE %s\n", fe->dai_link->name); + + /* call hw_free on the frontend */ + err = soc_pcm_hw_free(substream); + if (err < 0) + dev_err(fe->dev,"dpcm: hw_free FE %s failed\n", + fe->dai_link->name); + + /* only hw_params backends that are either sinks or sources + * to this frontend DAI */ + 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; + + mutex_unlock(&fe->card->mutex); + return 0; +} + +static int dpcm_be_dai_hw_params(struct snd_soc_pcm_runtime *fe, int stream) +{ + struct snd_soc_dpcm *dpcm; + int ret; + + list_for_each_entry(dpcm, &fe->dpcm[stream].be_clients, list_be) { + + struct snd_soc_pcm_runtime *be = dpcm->be; + struct snd_pcm_substream *be_substream = + snd_soc_dpcm_get_substream(be, stream); + + /* is this op for this BE ? */ + if (!snd_soc_dpcm_be_can_update(fe, be, stream)) + continue; + + /* only allow hw_params() if no connected FEs are running */ + if (!snd_soc_dpcm_can_be_params(fe, be, stream)) + continue; + + if ((be->dpcm[stream].state != SND_SOC_DPCM_STATE_OPEN) && + (be->dpcm[stream].state != SND_SOC_DPCM_STATE_HW_PARAMS) && + (be->dpcm[stream].state != SND_SOC_DPCM_STATE_HW_FREE)) + continue; + + dev_dbg(be->dev, "dpcm: hw_params BE %s\n", + dpcm->fe->dai_link->name); + + /* copy params for each dpcm */ + memcpy(&dpcm->hw_params, &fe->dpcm[stream].hw_params, + sizeof(struct snd_pcm_hw_params)); + + /* perform any hw_params fixups */ + if (be->dai_link->be_hw_params_fixup) { + ret = be->dai_link->be_hw_params_fixup(be, + &dpcm->hw_params); + if (ret < 0) { + dev_err(be->dev, + "dpcm: hw_params BE fixup failed %d\n", + ret); + goto unwind; + } + } + + ret = soc_pcm_hw_params(be_substream, &dpcm->hw_params); + if (ret < 0) { + dev_err(dpcm->be->dev, + "dpcm: hw_params BE failed %d\n", ret); + goto unwind; + } + + be->dpcm[stream].state = SND_SOC_DPCM_STATE_HW_PARAMS; + } + return 0; + +unwind: + /* disable any enabled and non active backends */ + list_for_each_entry_continue_reverse(dpcm, &fe->dpcm[stream].be_clients, list_be) { + struct snd_soc_pcm_runtime *be = dpcm->be; + struct snd_pcm_substream *be_substream = + snd_soc_dpcm_get_substream(be, stream); + + if (!snd_soc_dpcm_be_can_update(fe, be, stream)) + continue; + + /* only allow hw_free() if no connected FEs are running */ + if (!snd_soc_dpcm_can_be_free_stop(fe, be, stream)) + continue; + + if ((be->dpcm[stream].state != SND_SOC_DPCM_STATE_OPEN) && + (be->dpcm[stream].state != SND_SOC_DPCM_STATE_HW_PARAMS) && + (be->dpcm[stream].state != SND_SOC_DPCM_STATE_HW_FREE) && + (be->dpcm[stream].state != SND_SOC_DPCM_STATE_STOP)) + continue; + + soc_pcm_hw_free(be_substream); + } + + return ret; +} + +static int dpcm_fe_dai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *fe = substream->private_data; + 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; + + memcpy(&fe->dpcm[substream->stream].hw_params, params, + sizeof(struct snd_pcm_hw_params)); + ret = dpcm_be_dai_hw_params(fe, substream->stream); + if (ret < 0) { + dev_err(fe->dev,"dpcm: hw_params BE failed %d\n", ret); + goto out; + } + + dev_dbg(fe->dev, "dpcm: hw_params FE %s rate %d chan %x fmt %d\n", + fe->dai_link->name, params_rate(params), + params_channels(params), params_format(params)); + + /* call hw_params on the frontend */ + ret = soc_pcm_hw_params(substream, params); + if (ret < 0) { + dev_err(fe->dev,"dpcm: hw_params FE failed %d\n", ret); + dpcm_be_dai_hw_free(fe, stream); + } else + fe->dpcm[stream].state = SND_SOC_DPCM_STATE_HW_PARAMS; + +out: + fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_NO; + mutex_unlock(&fe->card->mutex); + return ret; +} + +static int dpcm_do_trigger(struct snd_soc_dpcm *dpcm, + struct snd_pcm_substream *substream, int cmd) +{ + int ret; + + dev_dbg(dpcm->be->dev, "dpcm: trigger BE %s cmd %d\n", + dpcm->fe->dai_link->name, cmd); + + ret = soc_pcm_trigger(substream, cmd); + if (ret < 0) + dev_err(dpcm->be->dev,"dpcm: trigger BE failed %d\n", ret); + + return ret; +} + +static int dpcm_be_dai_trigger(struct snd_soc_pcm_runtime *fe, int stream, + int cmd) +{ + struct snd_soc_dpcm *dpcm; + int ret = 0; + + list_for_each_entry(dpcm, &fe->dpcm[stream].be_clients, list_be) { + + struct snd_soc_pcm_runtime *be = dpcm->be; + struct snd_pcm_substream *be_substream = + snd_soc_dpcm_get_substream(be, stream); + + /* is this op for this BE ? */ + if (!snd_soc_dpcm_be_can_update(fe, be, stream)) + continue; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + if ((be->dpcm[stream].state != SND_SOC_DPCM_STATE_PREPARE) && + (be->dpcm[stream].state != SND_SOC_DPCM_STATE_STOP)) + continue; + + ret = dpcm_do_trigger(dpcm, be_substream, cmd); + if (ret) + return ret; + + be->dpcm[stream].state = SND_SOC_DPCM_STATE_START; + break; + case SNDRV_PCM_TRIGGER_RESUME: + if ((be->dpcm[stream].state != SND_SOC_DPCM_STATE_SUSPEND)) + continue; + + ret = dpcm_do_trigger(dpcm, be_substream, cmd); + if (ret) + return ret; + + be->dpcm[stream].state = SND_SOC_DPCM_STATE_START; + break; + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if ((be->dpcm[stream].state != SND_SOC_DPCM_STATE_PAUSED)) + continue; + + ret = dpcm_do_trigger(dpcm, be_substream, cmd); + if (ret) + return ret; + + be->dpcm[stream].state = SND_SOC_DPCM_STATE_START; + break; + case SNDRV_PCM_TRIGGER_STOP: + if (be->dpcm[stream].state != SND_SOC_DPCM_STATE_START) + continue; + + if (!snd_soc_dpcm_can_be_free_stop(fe, be, stream)) + continue; + + ret = dpcm_do_trigger(dpcm, be_substream, cmd); + if (ret) + return ret; + + be->dpcm[stream].state = SND_SOC_DPCM_STATE_STOP; + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + if (be->dpcm[stream].state != SND_SOC_DPCM_STATE_STOP) + continue; + + if (!snd_soc_dpcm_can_be_free_stop(fe, be, stream)) + continue; + + ret = dpcm_do_trigger(dpcm, be_substream, cmd); + if (ret) + return ret; + + be->dpcm[stream].state = SND_SOC_DPCM_STATE_SUSPEND; + break; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (be->dpcm[stream].state != SND_SOC_DPCM_STATE_START) + continue; + + if (!snd_soc_dpcm_can_be_free_stop(fe, be, stream)) + continue; + + ret = dpcm_do_trigger(dpcm, be_substream, cmd); + if (ret) + return ret; + + be->dpcm[stream].state = SND_SOC_DPCM_STATE_PAUSED; + break; + } + } + + return ret; +} +EXPORT_SYMBOL_GPL(dpcm_be_dai_trigger); + +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, ret; + enum snd_soc_dpcm_trigger trigger = fe->dai_link->trigger[stream]; + + fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_FE; + + switch (trigger) { + case SND_SOC_DPCM_TRIGGER_PRE: + /* call trigger on the frontend before the backend. */ + + dev_dbg(fe->dev, "dpcm: pre trigger FE %s cmd %d\n", + fe->dai_link->name, cmd); + + ret = soc_pcm_trigger(substream, cmd); + if (ret < 0) { + dev_err(fe->dev,"dpcm: trigger FE failed %d\n", ret); + goto out; + } + + ret = dpcm_be_dai_trigger(fe, substream->stream, cmd); + break; + case SND_SOC_DPCM_TRIGGER_POST: + /* call trigger on the frontend after the backend. */ + + ret = dpcm_be_dai_trigger(fe, substream->stream, cmd); + if (ret < 0) { + dev_err(fe->dev,"dpcm: trigger FE failed %d\n", ret); + goto out; + } + + dev_dbg(fe->dev, "dpcm: post trigger FE %s cmd %d\n", + fe->dai_link->name, cmd); + + ret = soc_pcm_trigger(substream, cmd); + break; + case SND_SOC_DPCM_TRIGGER_BESPOKE: + /* bespoke trigger() - handles both FE and BEs */ + + dev_dbg(fe->dev, "dpcm: bespoke trigger FE %s cmd %d\n", + fe->dai_link->name, cmd); + + ret = soc_pcm_bespoke_trigger(substream, cmd); + if (ret < 0) { + dev_err(fe->dev,"dpcm: trigger FE failed %d\n", ret); + goto out; + } + break; + default: + dev_err(fe->dev, "dpcm: invalid trigger cmd %d for %s\n", cmd, + fe->dai_link->name); + ret = -EINVAL; + goto out; + } + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + fe->dpcm[stream].state = SND_SOC_DPCM_STATE_START; + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + fe->dpcm[stream].state = SND_SOC_DPCM_STATE_STOP; + break; + } + +out: + fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_NO; + return ret; +} + +static int dpcm_be_dai_prepare(struct snd_soc_pcm_runtime *fe, int stream) +{ + struct snd_soc_dpcm *dpcm; + int ret = 0; + + list_for_each_entry(dpcm, &fe->dpcm[stream].be_clients, list_be) { + + struct snd_soc_pcm_runtime *be = dpcm->be; + struct snd_pcm_substream *be_substream = + snd_soc_dpcm_get_substream(be, stream); + + /* is this op for this BE ? */ + if (!snd_soc_dpcm_be_can_update(fe, be, stream)) + continue; + + if ((be->dpcm[stream].state != SND_SOC_DPCM_STATE_HW_PARAMS) && + (be->dpcm[stream].state != SND_SOC_DPCM_STATE_STOP)) + continue; + + dev_dbg(be->dev, "dpcm: prepare BE %s\n", + dpcm->fe->dai_link->name); + + ret = soc_pcm_prepare(be_substream); + if (ret < 0) { + dev_err(be->dev, "dpcm: backend prepare failed %d\n", + ret); + break; + } + + be->dpcm[stream].state = SND_SOC_DPCM_STATE_PREPARE; + } + return ret; +} + +static int dpcm_fe_dai_prepare(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *fe = substream->private_data; + int stream = substream->stream, ret = 0; + + mutex_lock_nested(&fe->card->mutex, SND_SOC_CARD_CLASS_RUNTIME); + + dev_dbg(fe->dev, "dpcm: prepare FE %s\n", fe->dai_link->name); + + fe->dpcm[stream].runtime_update = 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)) { + dev_err(fe->dev, "dpcm: no backend DAIs enabled for %s\n", + fe->dai_link->name); + ret = -EINVAL; + goto out; + } + + ret = dpcm_be_dai_prepare(fe, substream->stream); + if (ret < 0) + goto out; + + /* call prepare on the frontend */ + ret = soc_pcm_prepare(substream); + if (ret < 0) { + dev_err(fe->dev,"dpcm: prepare FE %s failed\n", + fe->dai_link->name); + goto out; + } + + /* run the stream event for each BE */ + dpcm_dapm_stream_event(fe, stream, SND_SOC_DAPM_STREAM_START); + fe->dpcm[stream].state = SND_SOC_DPCM_STATE_PREPARE; + +out: + fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_NO; + mutex_unlock(&fe->card->mutex); + + return ret; +} + +static int soc_pcm_ioctl(struct snd_pcm_substream *substream, + unsigned int cmd, void *arg) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_platform *platform = rtd->platform; + + if (platform->driver->ops->ioctl) + return platform->driver->ops->ioctl(substream, cmd, arg); + return snd_pcm_lib_ioctl(substream, cmd, arg); +} + +static int dpcm_run_update_shutdown(struct snd_soc_pcm_runtime *fe, int stream) +{ + struct snd_pcm_substream *substream = + snd_soc_dpcm_get_substream(fe, stream); + enum snd_soc_dpcm_trigger trigger = fe->dai_link->trigger[stream]; + int err; + + dev_dbg(fe->dev, "runtime %s close on FE %s\n", + stream ? "capture" : "playback", fe->dai_link->name); + + if (trigger == SND_SOC_DPCM_TRIGGER_BESPOKE) { + /* call bespoke trigger - FE takes care of all BE triggers */ + dev_dbg(fe->dev, "dpcm: bespoke trigger FE %s cmd stop\n", + fe->dai_link->name); + + err = soc_pcm_bespoke_trigger(substream, SNDRV_PCM_TRIGGER_STOP); + if (err < 0) + dev_err(fe->dev,"dpcm: trigger FE failed %d\n", err); + } else { + dev_dbg(fe->dev, "dpcm: trigger FE %s cmd stop\n", + fe->dai_link->name); + + err = dpcm_be_dai_trigger(fe, stream, SNDRV_PCM_TRIGGER_STOP); + if (err < 0) + dev_err(fe->dev,"dpcm: trigger FE failed %d\n", err); + } + + err = dpcm_be_dai_hw_free(fe, stream); + if (err < 0) + dev_err(fe->dev,"dpcm: hw_free FE failed %d\n", err); + + err = dpcm_be_dai_shutdown(fe, stream); + if (err < 0) + dev_err(fe->dev,"dpcm: shutdown FE failed %d\n", err); + + /* run the stream event for each BE */ + dpcm_dapm_stream_event(fe, stream, SND_SOC_DAPM_STREAM_NOP); + + return 0; +} + +static int dpcm_run_update_startup(struct snd_soc_pcm_runtime *fe, int stream) +{ + struct snd_pcm_substream *substream = + snd_soc_dpcm_get_substream(fe, stream); + struct snd_soc_dpcm *dpcm; + enum snd_soc_dpcm_trigger trigger = fe->dai_link->trigger[stream]; + int ret; + + dev_dbg(fe->dev, "runtime %s open on FE %s\n", + stream ? "capture" : "playback", fe->dai_link->name); + + /* Only start the BE if the FE is ready */ + if (fe->dpcm[stream].state == SND_SOC_DPCM_STATE_HW_FREE || + fe->dpcm[stream].state == SND_SOC_DPCM_STATE_CLOSE) + return -EINVAL; + + /* startup must always be called for new BEs */ + ret = dpcm_be_dai_startup(fe, stream); + if (ret < 0) { + goto disconnect; + return ret; + } + + /* keep going if FE state is > open */ + if (fe->dpcm[stream].state == SND_SOC_DPCM_STATE_OPEN) + return 0; + + ret = dpcm_be_dai_hw_params(fe, stream); + if (ret < 0) { + goto close; + return ret; + } + + /* keep going if FE state is > hw_params */ + if (fe->dpcm[stream].state == SND_SOC_DPCM_STATE_HW_PARAMS) + return 0; + + + ret = dpcm_be_dai_prepare(fe, stream); + if (ret < 0) { + goto hw_free; + return ret; + } + + /* run the stream event for each BE */ + dpcm_dapm_stream_event(fe, stream, SND_SOC_DAPM_STREAM_NOP); + + /* keep going if FE state is > prepare */ + if (fe->dpcm[stream].state == SND_SOC_DPCM_STATE_PREPARE || + fe->dpcm[stream].state == SND_SOC_DPCM_STATE_STOP) + return 0; + + if (trigger == SND_SOC_DPCM_TRIGGER_BESPOKE) { + /* call trigger on the frontend - FE takes care of all BE triggers */ + dev_dbg(fe->dev, "dpcm: bespoke trigger FE %s cmd start\n", + fe->dai_link->name); + + ret = soc_pcm_bespoke_trigger(substream, SNDRV_PCM_TRIGGER_START); + if (ret < 0) { + dev_err(fe->dev,"dpcm: bespoke trigger FE failed %d\n", ret); + goto hw_free; + } + } else { + dev_dbg(fe->dev, "dpcm: trigger FE %s cmd start\n", + fe->dai_link->name); + + ret = dpcm_be_dai_trigger(fe, stream, + SNDRV_PCM_TRIGGER_START); + if (ret < 0) { + dev_err(fe->dev,"dpcm: trigger FE failed %d\n", ret); + goto hw_free; + } + } + + return 0; + +hw_free: + dpcm_be_dai_hw_free(fe, stream); +close: + dpcm_be_dai_shutdown(fe, stream); +disconnect: + /* disconnect any non started BEs */ + list_for_each_entry(dpcm, &fe->dpcm[stream].be_clients, list_be) { + struct snd_soc_pcm_runtime *be = dpcm->be; + if (be->dpcm[stream].state != SND_SOC_DPCM_STATE_START) + dpcm->state = SND_SOC_DPCM_LINK_STATE_FREE; + } + + return ret; +} + +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; + ret = dpcm_run_update_startup(fe, stream); + if (ret < 0) + dev_err(fe->dev, "failed to startup some BEs\n"); + fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_NO; + + return ret; +} + +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; + ret = dpcm_run_update_shutdown(fe, stream); + if (ret < 0) + dev_err(fe->dev, "failed to shutdown some BEs\n"); + fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_NO; + + return ret; +} + +/* Called by DAPM mixer/mux changes to update audio routing between PCMs and + * any DAI links. + */ +int soc_dpcm_runtime_update(struct snd_soc_dapm_widget *widget) +{ + struct snd_soc_card *card; + int i, old, new, paths; + + if (widget->codec) + card = widget->codec->card; + else if (widget->platform) + card = widget->platform->card; + else + return -EINVAL; + + mutex_lock_nested(&card->mutex, SND_SOC_CARD_CLASS_RUNTIME); + for (i = 0; i < card->num_rtd; i++) { + struct snd_soc_dapm_widget_list *list; + struct snd_soc_pcm_runtime *fe = &card->rtd[i]; + + /* make sure link is FE */ + if (!fe->dai_link->dynamic) + continue; + + /* only check active links */ + if (!fe->cpu_dai->active) + continue; + + /* DAPM sync will call this to update DSP paths */ + dev_dbg(fe->dev, "DPCM runtime update for FE %s\n", + fe->dai_link->name); + + /* skip if FE doesn't have playback capability */ + if (!fe->cpu_dai->driver->playback.channels_min) + goto capture; + + paths = dpcm_path_get(fe, SNDRV_PCM_STREAM_PLAYBACK, &list); + if (paths < 0) { + dev_warn(fe->dev, "%s no valid %s path\n", + fe->dai_link->name, "playback"); + mutex_unlock(&card->mutex); + return paths; + } + + /* update any new playback paths */ + new = dpcm_process_paths(fe, SNDRV_PCM_STREAM_PLAYBACK, &list, 1); + if (new) { + dpcm_run_new_update(fe, SNDRV_PCM_STREAM_PLAYBACK); + dpcm_clear_pending_state(fe, SNDRV_PCM_STREAM_PLAYBACK); + dpcm_be_disconnect(fe, SNDRV_PCM_STREAM_PLAYBACK); + } + + /* update any old playback paths */ + old = dpcm_process_paths(fe, SNDRV_PCM_STREAM_PLAYBACK, &list, 0); + if (old) { + dpcm_run_old_update(fe, SNDRV_PCM_STREAM_PLAYBACK); + dpcm_clear_pending_state(fe, SNDRV_PCM_STREAM_PLAYBACK); + dpcm_be_disconnect(fe, SNDRV_PCM_STREAM_PLAYBACK); + } + +capture: + /* skip if FE doesn't have capture capability */ + if (!fe->cpu_dai->driver->capture.channels_min) + continue; + + paths = dpcm_path_get(fe, SNDRV_PCM_STREAM_CAPTURE, &list); + if (paths < 0) { + dev_warn(fe->dev, "%s no valid %s path\n", + fe->dai_link->name, "capture"); + mutex_unlock(&card->mutex); + return paths; + } + + /* update any new capture paths */ + new = dpcm_process_paths(fe, SNDRV_PCM_STREAM_CAPTURE, &list, 1); + if (new) { + dpcm_run_new_update(fe, SNDRV_PCM_STREAM_CAPTURE); + dpcm_clear_pending_state(fe, SNDRV_PCM_STREAM_CAPTURE); + dpcm_be_disconnect(fe, SNDRV_PCM_STREAM_CAPTURE); + } + + /* update any old capture paths */ + old = dpcm_process_paths(fe, SNDRV_PCM_STREAM_CAPTURE, &list, 0); + if (old) { + dpcm_run_old_update(fe, SNDRV_PCM_STREAM_CAPTURE); + dpcm_clear_pending_state(fe, SNDRV_PCM_STREAM_CAPTURE); + dpcm_be_disconnect(fe, SNDRV_PCM_STREAM_CAPTURE); + } + + dpcm_path_put(&list); + } + + mutex_unlock(&card->mutex); + return 0; +} +int soc_dpcm_be_digital_mute(struct snd_soc_pcm_runtime *fe, int mute) +{ + struct snd_soc_dpcm *dpcm; + struct list_head *clients = + &fe->dpcm[SNDRV_PCM_STREAM_PLAYBACK].be_clients; + + list_for_each_entry(dpcm, clients, list_be) { + + struct snd_soc_pcm_runtime *be = dpcm->be; + struct snd_soc_dai *dai = be->codec_dai; + struct snd_soc_dai_driver *drv = dai->driver; + + if (be->dai_link->ignore_suspend) + continue; + + dev_dbg(be->dev, "BE digital mute %s\n", be->dai_link->name); + + if (drv->ops->digital_mute && dai->playback_active) + drv->ops->digital_mute(dai, mute); + } + + return 0; +} + +static int dpcm_fe_dai_open(struct snd_pcm_substream *fe_substream) +{ + struct snd_soc_pcm_runtime *fe = fe_substream->private_data; + struct snd_soc_dpcm *dpcm; + struct snd_soc_dapm_widget_list *list; + int ret; + int stream = fe_substream->stream; + + mutex_lock_nested(&fe->card->mutex, SND_SOC_CARD_CLASS_RUNTIME); + fe->dpcm[stream].runtime = fe_substream->runtime; + + if (dpcm_path_get(fe, stream, &list) <= 0) { + dev_warn(fe->dev, "asoc: %s no valid %s route\n", + fe->dai_link->name, stream ? "capture" : "playback"); + mutex_unlock(&fe->card->mutex); + return -EINVAL; + } + + /* calculate valid and active FE <-> BE dpcms */ + dpcm_process_paths(fe, stream, &list, 1); + + ret = dpcm_fe_dai_startup(fe_substream); + if (ret < 0) { + /* clean up all links */ + list_for_each_entry(dpcm, &fe->dpcm[stream].be_clients, list_be) + dpcm->state = SND_SOC_DPCM_LINK_STATE_FREE; + + dpcm_be_disconnect(fe, stream); + fe->dpcm[stream].runtime = NULL; + } + + dpcm_clear_pending_state(fe, stream); + dpcm_path_put(&list); + mutex_unlock(&fe->card->mutex); + return ret; +} + +static int dpcm_fe_dai_close(struct snd_pcm_substream *fe_substream) +{ + struct snd_soc_pcm_runtime *fe = fe_substream->private_data; + struct snd_soc_dpcm *dpcm; + int stream = fe_substream->stream, ret; + + mutex_lock_nested(&fe->card->mutex, SND_SOC_CARD_CLASS_RUNTIME); + ret = dpcm_fe_dai_shutdown(fe_substream); + + /* mark FE's links ready to prune */ + list_for_each_entry(dpcm, &fe->dpcm[stream].be_clients, list_be) + dpcm->state = SND_SOC_DPCM_LINK_STATE_FREE; + + dpcm_be_disconnect(fe, stream); + + fe->dpcm[stream].runtime = NULL; + mutex_unlock(&fe->card->mutex); + return ret; +} + /* create a new pcm */ int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num) { @@ -641,56 +2001,94 @@ int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num) struct snd_soc_platform *platform = rtd->platform; struct snd_soc_dai *codec_dai = rtd->codec_dai; struct snd_soc_dai *cpu_dai = rtd->cpu_dai; - struct snd_pcm_ops *soc_pcm_ops = &rtd->ops; struct snd_pcm *pcm; char new_name[64]; int ret = 0, playback = 0, capture = 0; - soc_pcm_ops->open = soc_pcm_open; - soc_pcm_ops->close = soc_pcm_close; - soc_pcm_ops->hw_params = soc_pcm_hw_params; - soc_pcm_ops->hw_free = soc_pcm_hw_free; - soc_pcm_ops->prepare = soc_pcm_prepare; - soc_pcm_ops->trigger = soc_pcm_trigger; - soc_pcm_ops->pointer = soc_pcm_pointer; - - /* check client and interface hw capabilities */ - snprintf(new_name, sizeof(new_name), "%s %s-%d", - rtd->dai_link->stream_name, codec_dai->name, num); - - if (codec_dai->driver->playback.channels_min) - playback = 1; - if (codec_dai->driver->capture.channels_min) - capture = 1; - - dev_dbg(rtd->card->dev, "registered pcm #%d %s\n",num,new_name); - ret = snd_pcm_new(rtd->card->snd_card, new_name, - num, playback, capture, &pcm); + if (rtd->dai_link->dynamic || rtd->dai_link->no_pcm) { + if (cpu_dai->driver->playback.channels_min) + playback = 1; + if (cpu_dai->driver->capture.channels_min) + capture = 1; + } else { + if (codec_dai->driver->playback.channels_min) + playback = 1; + if (codec_dai->driver->capture.channels_min) + capture = 1; + } + + /* create the PCM */ + if (rtd->dai_link->no_pcm) { + snprintf(new_name, sizeof(new_name), "(%s)", + rtd->dai_link->stream_name); + + ret = snd_pcm_new_internal(rtd->card->snd_card, new_name, num, + playback, capture, &pcm); + } else { + if (rtd->dai_link->dynamic) + snprintf(new_name, sizeof(new_name), "%s (*)", + rtd->dai_link->stream_name); + else + snprintf(new_name, sizeof(new_name), "%s %s-%d", + rtd->dai_link->stream_name, codec_dai->name, num); + + ret = snd_pcm_new(rtd->card->snd_card, new_name, num, playback, + capture, &pcm); + } if (ret < 0) { printk(KERN_ERR "asoc: can't create pcm for codec %s\n", codec->name); return ret; } + dev_dbg(rtd->card->dev, "registered pcm #%d %s\n",num, new_name); /* DAPM dai link stream work */ INIT_DELAYED_WORK(&rtd->delayed_work, close_delayed_work); rtd->pcm = pcm; pcm->private_data = rtd; + + if (rtd->dai_link->no_pcm) { + if (playback) + pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream->private_data = rtd; + if (capture) + pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream->private_data = rtd; + goto out; + } + + /* ASoC PCM operations */ + if (rtd->dai_link->dynamic) { + rtd->ops.open = dpcm_fe_dai_open; + rtd->ops.hw_params = dpcm_fe_dai_hw_params; + rtd->ops.prepare = dpcm_fe_dai_prepare; + rtd->ops.trigger = dpcm_fe_dai_trigger; + rtd->ops.hw_free = dpcm_fe_dai_hw_free; + rtd->ops.close = dpcm_fe_dai_close; + rtd->ops.pointer = soc_pcm_pointer; + rtd->ops.ioctl = soc_pcm_ioctl; + } else { + rtd->ops.open = soc_pcm_open; + rtd->ops.hw_params = soc_pcm_hw_params; + rtd->ops.prepare = soc_pcm_prepare; + rtd->ops.trigger = soc_pcm_trigger; + rtd->ops.hw_free = soc_pcm_hw_free; + rtd->ops.close = soc_pcm_close; + rtd->ops.pointer = soc_pcm_pointer; + rtd->ops.ioctl = soc_pcm_ioctl; + } + if (platform->driver->ops) { - soc_pcm_ops->mmap = platform->driver->ops->mmap; - soc_pcm_ops->pointer = platform->driver->ops->pointer; - soc_pcm_ops->ioctl = platform->driver->ops->ioctl; - soc_pcm_ops->copy = platform->driver->ops->copy; - soc_pcm_ops->silence = platform->driver->ops->silence; - soc_pcm_ops->ack = platform->driver->ops->ack; - soc_pcm_ops->page = platform->driver->ops->page; + rtd->ops.ack = platform->driver->ops->ack; + rtd->ops.copy = platform->driver->ops->copy; + rtd->ops.silence = platform->driver->ops->silence; + rtd->ops.page = platform->driver->ops->page; + rtd->ops.mmap = platform->driver->ops->mmap; } if (playback) - snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, soc_pcm_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &rtd->ops); if (capture) - snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, soc_pcm_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &rtd->ops); if (platform->driver->pcm_new) { ret = platform->driver->pcm_new(rtd); @@ -701,7 +2099,257 @@ int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num) } pcm->private_free = platform->driver->pcm_free; +out: printk(KERN_INFO "asoc: %s <-> %s mapping ok\n", codec_dai->name, cpu_dai->name); return ret; } + +/* is the current PCM operation for this FE ? */ +int snd_soc_dpcm_fe_can_update(struct snd_soc_pcm_runtime *fe, int stream) +{ + if (fe->dpcm[stream].runtime_update == SND_SOC_DPCM_UPDATE_FE) + return 1; + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_dpcm_fe_can_update); + +/* is the current PCM operation for this BE ? */ +int snd_soc_dpcm_be_can_update(struct snd_soc_pcm_runtime *fe, + struct snd_soc_pcm_runtime *be, int stream) +{ + if ((fe->dpcm[stream].runtime_update == SND_SOC_DPCM_UPDATE_FE) || + ((fe->dpcm[stream].runtime_update == SND_SOC_DPCM_UPDATE_BE) && + be->dpcm[stream].runtime_update)) + return 1; + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_dpcm_be_can_update); + +/* get the substream for this BE */ +struct snd_pcm_substream * + snd_soc_dpcm_get_substream(struct snd_soc_pcm_runtime *be, int stream) +{ + return be->pcm->streams[stream].substream; +} +EXPORT_SYMBOL_GPL(snd_soc_dpcm_get_substream); + +/* get the BE runtime state */ +enum snd_soc_dpcm_state + snd_soc_dpcm_be_get_state(struct snd_soc_pcm_runtime *be, int stream) +{ + return be->dpcm[stream].state; +} +EXPORT_SYMBOL_GPL(snd_soc_dpcm_be_get_state); + +/* set the BE runtime state */ +void snd_soc_dpcm_be_set_state(struct snd_soc_pcm_runtime *be, + int stream, enum snd_soc_dpcm_state state) +{ + be->dpcm[stream].state = state; +} +EXPORT_SYMBOL_GPL(snd_soc_dpcm_be_set_state); + +/* + * We can only hw_free, stop, pause or suspend a BE DAI if any of it's FE + * are not running, paused or suspended for the specified stream direction. + */ +int snd_soc_dpcm_can_be_free_stop(struct snd_soc_pcm_runtime *fe, + struct snd_soc_pcm_runtime *be, int stream) +{ + struct snd_soc_dpcm *dpcm; + int state; + + list_for_each_entry(dpcm, &be->dpcm[stream].fe_clients, list_fe) { + + if (dpcm->fe == fe) + continue; + + state = dpcm->fe->dpcm[stream].state; + if (state == SND_SOC_DPCM_STATE_START || + state == SND_SOC_DPCM_STATE_PAUSED || + state == SND_SOC_DPCM_STATE_SUSPEND) + return 0; + } + + /* it's safe to free/stop this BE DAI */ + return 1; +} +EXPORT_SYMBOL_GPL(snd_soc_dpcm_can_be_free_stop); + +/* + * We can only change hw params a BE DAI if any of it's FE are not prepared, + * running, paused or suspended for the specified stream direction. + */ +int snd_soc_dpcm_can_be_params(struct snd_soc_pcm_runtime *fe, + struct snd_soc_pcm_runtime *be, int stream) +{ + struct snd_soc_dpcm *dpcm; + int state; + + list_for_each_entry(dpcm, &be->dpcm[stream].fe_clients, list_fe) { + + if (dpcm->fe == fe) + continue; + + state = dpcm->fe->dpcm[stream].state; + if (state == SND_SOC_DPCM_STATE_START || + state == SND_SOC_DPCM_STATE_PAUSED || + state == SND_SOC_DPCM_STATE_SUSPEND || + state == SND_SOC_DPCM_STATE_PREPARE) + return 0; + } + + /* it's safe to change hw_params */ + return 1; +} +EXPORT_SYMBOL_GPL(snd_soc_dpcm_can_be_params); + +int snd_soc_platform_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_platform *platform) +{ + if (platform->driver->ops->trigger) + return platform->driver->ops->trigger(substream, cmd); + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_platform_trigger); + +#ifdef CONFIG_DEBUG_FS +static char *dpcm_state_string(enum snd_soc_dpcm_state state) +{ + switch (state) { + case SND_SOC_DPCM_STATE_NEW: + return "new"; + case SND_SOC_DPCM_STATE_OPEN: + return "open"; + case SND_SOC_DPCM_STATE_HW_PARAMS: + return "hw_params"; + case SND_SOC_DPCM_STATE_PREPARE: + return "prepare"; + case SND_SOC_DPCM_STATE_START: + return "start"; + case SND_SOC_DPCM_STATE_STOP: + return "stop"; + case SND_SOC_DPCM_STATE_SUSPEND: + return "suspend"; + case SND_SOC_DPCM_STATE_PAUSED: + return "paused"; + case SND_SOC_DPCM_STATE_HW_FREE: + return "hw_free"; + case SND_SOC_DPCM_STATE_CLOSE: + return "close"; + } + + return "unknown"; +} + +static ssize_t dpcm_show_state(struct snd_soc_pcm_runtime *fe, + int stream, char *buf, size_t size) +{ + struct snd_pcm_hw_params *params = &fe->dpcm[stream].hw_params; + struct snd_soc_dpcm *dpcm; + ssize_t offset = 0; + + /* FE state */ + offset += snprintf(buf + offset, size - offset, + "[%s - %s]\n", fe->dai_link->name, + stream ? "Capture" : "Playback"); + + offset += snprintf(buf + offset, size - offset, "State: %s\n", + dpcm_state_string(fe->dpcm[stream].state)); + + if ((fe->dpcm[stream].state >= SND_SOC_DPCM_STATE_HW_PARAMS) && + (fe->dpcm[stream].state <= SND_SOC_DPCM_STATE_STOP)) + offset += snprintf(buf + offset, size - offset, + "Hardware Params: " + "Format = %s, Channels = %d, Rate = %d\n", + snd_pcm_format_name(params_format(params)), + params_channels(params), + params_rate(params)); + + /* BEs state */ + offset += snprintf(buf + offset, size - offset, "Backends:\n"); + + if (list_empty(&fe->dpcm[stream].be_clients)) { + offset += snprintf(buf + offset, size - offset, + " No active DSP links\n"); + goto out; + } + + list_for_each_entry(dpcm, &fe->dpcm[stream].be_clients, list_be) { + struct snd_soc_pcm_runtime *be = dpcm->be; + params = &dpcm->hw_params; + + offset += snprintf(buf + offset, size - offset, + "- %s\n", be->dai_link->name); + + offset += snprintf(buf + offset, size - offset, + " State: %s\n", + dpcm_state_string(be->dpcm[stream].state)); + + if ((be->dpcm[stream].state >= SND_SOC_DPCM_STATE_HW_PARAMS) && + (be->dpcm[stream].state <= SND_SOC_DPCM_STATE_STOP)) + offset += snprintf(buf + offset, size - offset, + " Hardware Params: " + "Format = %s, Channels = %d, Rate = %d\n", + snd_pcm_format_name(params_format(params)), + params_channels(params), + params_rate(params)); + } + +out: + return offset; +} + +static ssize_t dpcm_state_read_file(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct snd_soc_pcm_runtime *fe = file->private_data; + ssize_t out_count = PAGE_SIZE, offset = 0, ret = 0; + char *buf; + + buf = kmalloc(out_count, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + if (fe->cpu_dai->driver->playback.channels_min) + offset += dpcm_show_state(fe, SNDRV_PCM_STREAM_PLAYBACK, + buf + offset, out_count - offset); + + if (fe->cpu_dai->driver->capture.channels_min) + offset += dpcm_show_state(fe, SNDRV_PCM_STREAM_CAPTURE, + buf + offset, out_count - offset); + + ret = simple_read_from_buffer(user_buf, count, ppos, buf, offset); + + kfree(buf); + return ret; +} + +static const struct file_operations dpcm_state_fops = { + .open = simple_open, + .read = dpcm_state_read_file, + .llseek = default_llseek, +}; + +int soc_dpcm_debugfs_add(struct snd_soc_pcm_runtime *rtd) +{ + if (!rtd->dai_link) + return 0; + + rtd->debugfs_dpcm_root = debugfs_create_dir(rtd->dai_link->name, + rtd->card->debugfs_card_root); + if (!rtd->debugfs_dpcm_root) { + dev_dbg(rtd->dev, + "ASoC: Failed to create dpcm debugfs directory %s\n", + rtd->dai_link->name); + return -EINVAL; + } + + rtd->debugfs_dpcm_state = debugfs_create_file("state", 0444, + rtd->debugfs_dpcm_root, + rtd, &dpcm_state_fops); + + return 0; +} +#endif diff --git a/sound/soc/tegra/Kconfig b/sound/soc/tegra/Kconfig index ce1b773c351..c1c8e955f4d 100644 --- a/sound/soc/tegra/Kconfig +++ b/sound/soc/tegra/Kconfig @@ -1,26 +1,63 @@ config SND_SOC_TEGRA tristate "SoC Audio for the Tegra System-on-Chip" depends on ARCH_TEGRA && TEGRA_SYSTEM_DMA + select REGMAP_MMIO help Say Y or M here if you want support for SoC audio on Tegra. -config SND_SOC_TEGRA_I2S +config SND_SOC_TEGRA20_DAS tristate - depends on SND_SOC_TEGRA + depends on SND_SOC_TEGRA && ARCH_TEGRA_2x_SOC + help + Say Y or M if you want to add support for the Tegra20 DAS module. + You will also need to select the individual machine drivers to + support below. + +config SND_SOC_TEGRA20_I2S + tristate + depends on SND_SOC_TEGRA && ARCH_TEGRA_2x_SOC + select SND_SOC_TEGRA20_DAS help Say Y or M if you want to add support for codecs attached to the - Tegra I2S interface. You will also need to select the individual + Tegra20 I2S interface. You will also need to select the individual machine drivers to support below. -config SND_SOC_TEGRA_SPDIF +config SND_SOC_TEGRA20_SPDIF tristate - depends on SND_SOC_TEGRA + depends on SND_SOC_TEGRA && ARCH_TEGRA_2x_SOC default m help - Say Y or M if you want to add support for the SPDIF interface. + Say Y or M if you want to add support for the Tegra20 SPDIF interface. You will also need to select the individual machine drivers to support below. +config SND_SOC_TEGRA30_AHUB + tristate + depends on SND_SOC_TEGRA && ARCH_TEGRA_3x_SOC + help + Say Y or M if you want to add support for the Tegra20 AHUB module. + You will also need to select the individual machine drivers to + support below. + +config SND_SOC_TEGRA30_I2S + tristate + depends on SND_SOC_TEGRA && ARCH_TEGRA_3x_SOC + select SND_SOC_TEGRA30_AHUB + help + Say Y or M if you want to add support for codecs attached to the + Tegra30 I2S interface. You will also need to select the individual + machine drivers to support below. + +config SND_SOC_TEGRA_WM8753 + tristate "SoC Audio support for Tegra boards using a WM8753 codec" + depends on SND_SOC_TEGRA && I2C + select SND_SOC_TEGRA20_I2S if ARCH_TEGRA_2x_SOC + select SND_SOC_TEGRA30_I2S if ARCH_TEGRA_3x_SOC + select SND_SOC_WM8753 + help + Say Y or M here if you want to add support for SoC audio on Tegra + boards using the WM8753 codec, such as Whistler. + config MACH_HAS_SND_SOC_TEGRA_WM8903 bool help @@ -32,7 +69,8 @@ config SND_SOC_TEGRA_WM8903 tristate "SoC Audio support for Tegra boards using a WM8903 codec" depends on SND_SOC_TEGRA && I2C depends on MACH_HAS_SND_SOC_TEGRA_WM8903 - select SND_SOC_TEGRA_I2S + select SND_SOC_TEGRA20_I2S if ARCH_TEGRA_2x_SOC + select SND_SOC_TEGRA30_I2S if ARCH_TEGRA_3x_SOC select SND_SOC_WM8903 help Say Y or M here if you want to add support for SoC audio on Tegra @@ -42,17 +80,17 @@ config SND_SOC_TEGRA_WM8903 config SND_SOC_TEGRA_TRIMSLICE tristate "SoC Audio support for TrimSlice board" depends on SND_SOC_TEGRA && MACH_TRIMSLICE && I2C - select SND_SOC_TEGRA_I2S + select SND_SOC_TEGRA20_I2S if ARCH_TEGRA_2x_SOC select SND_SOC_TLV320AIC23 help Say Y or M here if you want to add support for SoC audio on the TrimSlice platform. config SND_SOC_TEGRA_ALC5632 - tristate "SoC Audio support for Tegra boards using an ALC5632 codec" - depends on SND_SOC_TEGRA && I2C - select SND_SOC_TEGRA_I2S - select SND_SOC_ALC5632 - help - Say Y or M here if you want to add support for SoC audio on the - Toshiba AC100 netbook. + tristate "SoC Audio support for Tegra boards using an ALC5632 codec" + depends on SND_SOC_TEGRA && I2C + select SND_SOC_TEGRA20_I2S if ARCH_TEGRA_2x_SOC + select SND_SOC_ALC5632 + help + Say Y or M here if you want to add support for SoC audio on the + Toshiba AC100 netbook. diff --git a/sound/soc/tegra/Makefile b/sound/soc/tegra/Makefile index 8e584b8fcfb..391e78a34c0 100644 --- a/sound/soc/tegra/Makefile +++ b/sound/soc/tegra/Makefile @@ -1,21 +1,27 @@ # Tegra platform Support -snd-soc-tegra-das-objs := tegra_das.o snd-soc-tegra-pcm-objs := tegra_pcm.o -snd-soc-tegra-i2s-objs := tegra_i2s.o -snd-soc-tegra-spdif-objs := tegra_spdif.o snd-soc-tegra-utils-objs += tegra_asoc_utils.o +snd-soc-tegra20-das-objs := tegra20_das.o +snd-soc-tegra20-i2s-objs := tegra20_i2s.o +snd-soc-tegra20-spdif-objs := tegra20_spdif.o +snd-soc-tegra30-ahub-objs := tegra30_ahub.o +snd-soc-tegra30-i2s-objs := tegra30_i2s.o -obj-$(CONFIG_SND_SOC_TEGRA) += snd-soc-tegra-utils.o -obj-$(CONFIG_SND_SOC_TEGRA) += snd-soc-tegra-das.o obj-$(CONFIG_SND_SOC_TEGRA) += snd-soc-tegra-pcm.o -obj-$(CONFIG_SND_SOC_TEGRA_I2S) += snd-soc-tegra-i2s.o -obj-$(CONFIG_SND_SOC_TEGRA_SPDIF) += snd-soc-tegra-spdif.o +obj-$(CONFIG_SND_SOC_TEGRA) += snd-soc-tegra-utils.o +obj-$(CONFIG_SND_SOC_TEGRA20_DAS) += snd-soc-tegra20-das.o +obj-$(CONFIG_SND_SOC_TEGRA20_I2S) += snd-soc-tegra20-i2s.o +obj-$(CONFIG_SND_SOC_TEGRA20_SPDIF) += snd-soc-tegra20-spdif.o +obj-$(CONFIG_SND_SOC_TEGRA30_AHUB) += snd-soc-tegra30-ahub.o +obj-$(CONFIG_SND_SOC_TEGRA30_I2S) += snd-soc-tegra30-i2s.o # Tegra machine Support +snd-soc-tegra-wm8753-objs := tegra_wm8753.o snd-soc-tegra-wm8903-objs := tegra_wm8903.o snd-soc-tegra-trimslice-objs := trimslice.o snd-soc-tegra-alc5632-objs := tegra_alc5632.o +obj-$(CONFIG_SND_SOC_TEGRA_WM8753) += snd-soc-tegra-wm8753.o obj-$(CONFIG_SND_SOC_TEGRA_WM8903) += snd-soc-tegra-wm8903.o obj-$(CONFIG_SND_SOC_TEGRA_TRIMSLICE) += snd-soc-tegra-trimslice.o obj-$(CONFIG_SND_SOC_TEGRA_ALC5632) += snd-soc-tegra-alc5632.o diff --git a/sound/soc/tegra/tegra20_das.c b/sound/soc/tegra/tegra20_das.c new file mode 100644 index 00000000000..bf99296bce9 --- /dev/null +++ b/sound/soc/tegra/tegra20_das.c @@ -0,0 +1,233 @@ +/* + * tegra20_das.c - Tegra20 DAS driver + * + * Author: Stephen Warren <swarren@nvidia.com> + * Copyright (C) 2010 - NVIDIA, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * 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/device.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/slab.h> +#include <sound/soc.h> +#include "tegra20_das.h" + +#define DRV_NAME "tegra20-das" + +static struct tegra20_das *das; + +static inline void tegra20_das_write(u32 reg, u32 val) +{ + regmap_write(das->regmap, reg, val); +} + +static inline u32 tegra20_das_read(u32 reg) +{ + u32 val; + regmap_read(das->regmap, reg, &val); + return val; +} + +int tegra20_das_connect_dap_to_dac(int dap, int dac) +{ + u32 addr; + u32 reg; + + if (!das) + return -ENODEV; + + addr = TEGRA20_DAS_DAP_CTRL_SEL + + (dap * TEGRA20_DAS_DAP_CTRL_SEL_STRIDE); + reg = dac << TEGRA20_DAS_DAP_CTRL_SEL_DAP_CTRL_SEL_P; + + tegra20_das_write(addr, reg); + + return 0; +} +EXPORT_SYMBOL_GPL(tegra20_das_connect_dap_to_dac); + +int tegra20_das_connect_dap_to_dap(int dap, int otherdap, int master, + int sdata1rx, int sdata2rx) +{ + u32 addr; + u32 reg; + + if (!das) + return -ENODEV; + + addr = TEGRA20_DAS_DAP_CTRL_SEL + + (dap * TEGRA20_DAS_DAP_CTRL_SEL_STRIDE); + reg = otherdap << TEGRA20_DAS_DAP_CTRL_SEL_DAP_CTRL_SEL_P | + !!sdata2rx << TEGRA20_DAS_DAP_CTRL_SEL_DAP_SDATA2_TX_RX_P | + !!sdata1rx << TEGRA20_DAS_DAP_CTRL_SEL_DAP_SDATA1_TX_RX_P | + !!master << TEGRA20_DAS_DAP_CTRL_SEL_DAP_MS_SEL_P; + + tegra20_das_write(addr, reg); + + return 0; +} +EXPORT_SYMBOL_GPL(tegra20_das_connect_dap_to_dap); + +int tegra20_das_connect_dac_to_dap(int dac, int dap) +{ + u32 addr; + u32 reg; + + if (!das) + return -ENODEV; + + addr = TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL + + (dac * TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL_STRIDE); + reg = dap << TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_CLK_SEL_P | + dap << TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA1_SEL_P | + dap << TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA2_SEL_P; + + tegra20_das_write(addr, reg); + + return 0; +} +EXPORT_SYMBOL_GPL(tegra20_das_connect_dac_to_dap); + +#define LAST_REG(name) \ + (TEGRA20_DAS_##name + \ + (TEGRA20_DAS_##name##_STRIDE * (TEGRA20_DAS_##name##_COUNT - 1))) + +static bool tegra20_das_wr_rd_reg(struct device *dev, unsigned int reg) +{ + if ((reg >= TEGRA20_DAS_DAP_CTRL_SEL) && + (reg <= LAST_REG(DAP_CTRL_SEL))) + return true; + if ((reg >= TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL) && + (reg <= LAST_REG(DAC_INPUT_DATA_CLK_SEL))) + return true; + + return false; +} + +static const struct regmap_config tegra20_das_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = LAST_REG(DAC_INPUT_DATA_CLK_SEL), + .writeable_reg = tegra20_das_wr_rd_reg, + .readable_reg = tegra20_das_wr_rd_reg, + .cache_type = REGCACHE_RBTREE, +}; + +static int __devinit tegra20_das_probe(struct platform_device *pdev) +{ + struct resource *res, *region; + void __iomem *regs; + int ret = 0; + + if (das) + return -ENODEV; + + das = devm_kzalloc(&pdev->dev, sizeof(struct tegra20_das), GFP_KERNEL); + if (!das) { + dev_err(&pdev->dev, "Can't allocate tegra20_das\n"); + ret = -ENOMEM; + goto err; + } + das->dev = &pdev->dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "No memory resource\n"); + ret = -ENODEV; + goto err; + } + + region = devm_request_mem_region(&pdev->dev, res->start, + resource_size(res), pdev->name); + if (!region) { + dev_err(&pdev->dev, "Memory region already claimed\n"); + ret = -EBUSY; + goto err; + } + + regs = devm_ioremap(&pdev->dev, res->start, resource_size(res)); + if (!regs) { + dev_err(&pdev->dev, "ioremap failed\n"); + ret = -ENOMEM; + goto err; + } + + das->regmap = devm_regmap_init_mmio(&pdev->dev, regs, + &tegra20_das_regmap_config); + if (IS_ERR(das->regmap)) { + dev_err(&pdev->dev, "regmap init failed\n"); + ret = PTR_ERR(das->regmap); + goto err; + } + + ret = tegra20_das_connect_dap_to_dac(TEGRA20_DAS_DAP_ID_1, + TEGRA20_DAS_DAP_SEL_DAC1); + if (ret) { + dev_err(&pdev->dev, "Can't set up DAS DAP connection\n"); + goto err; + } + ret = tegra20_das_connect_dac_to_dap(TEGRA20_DAS_DAC_ID_1, + TEGRA20_DAS_DAC_SEL_DAP1); + if (ret) { + dev_err(&pdev->dev, "Can't set up DAS DAC connection\n"); + goto err; + } + + platform_set_drvdata(pdev, das); + + return 0; + +err: + das = NULL; + return ret; +} + +static int __devexit tegra20_das_remove(struct platform_device *pdev) +{ + if (!das) + return -ENODEV; + + das = NULL; + + return 0; +} + +static const struct of_device_id tegra20_das_of_match[] __devinitconst = { + { .compatible = "nvidia,tegra20-das", }, + {}, +}; + +static struct platform_driver tegra20_das_driver = { + .probe = tegra20_das_probe, + .remove = __devexit_p(tegra20_das_remove), + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + .of_match_table = tegra20_das_of_match, + }, +}; +module_platform_driver(tegra20_das_driver); + +MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>"); +MODULE_DESCRIPTION("Tegra20 DAS driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRV_NAME); +MODULE_DEVICE_TABLE(of, tegra20_das_of_match); diff --git a/sound/soc/tegra/tegra20_das.h b/sound/soc/tegra/tegra20_das.h new file mode 100644 index 00000000000..be217f3d3a7 --- /dev/null +++ b/sound/soc/tegra/tegra20_das.h @@ -0,0 +1,134 @@ +/* + * tegra20_das.h - Definitions for Tegra20 DAS driver + * + * Author: Stephen Warren <swarren@nvidia.com> + * Copyright (C) 2010,2012 - NVIDIA, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __TEGRA20_DAS_H__ +#define __TEGRA20_DAS_H__ + +/* Register TEGRA20_DAS_DAP_CTRL_SEL */ +#define TEGRA20_DAS_DAP_CTRL_SEL 0x00 +#define TEGRA20_DAS_DAP_CTRL_SEL_COUNT 5 +#define TEGRA20_DAS_DAP_CTRL_SEL_STRIDE 4 +#define TEGRA20_DAS_DAP_CTRL_SEL_DAP_MS_SEL_P 31 +#define TEGRA20_DAS_DAP_CTRL_SEL_DAP_MS_SEL_S 1 +#define TEGRA20_DAS_DAP_CTRL_SEL_DAP_SDATA1_TX_RX_P 30 +#define TEGRA20_DAS_DAP_CTRL_SEL_DAP_SDATA1_TX_RX_S 1 +#define TEGRA20_DAS_DAP_CTRL_SEL_DAP_SDATA2_TX_RX_P 29 +#define TEGRA20_DAS_DAP_CTRL_SEL_DAP_SDATA2_TX_RX_S 1 +#define TEGRA20_DAS_DAP_CTRL_SEL_DAP_CTRL_SEL_P 0 +#define TEGRA20_DAS_DAP_CTRL_SEL_DAP_CTRL_SEL_S 5 + +/* Values for field TEGRA20_DAS_DAP_CTRL_SEL_DAP_CTRL_SEL */ +#define TEGRA20_DAS_DAP_SEL_DAC1 0 +#define TEGRA20_DAS_DAP_SEL_DAC2 1 +#define TEGRA20_DAS_DAP_SEL_DAC3 2 +#define TEGRA20_DAS_DAP_SEL_DAP1 16 +#define TEGRA20_DAS_DAP_SEL_DAP2 17 +#define TEGRA20_DAS_DAP_SEL_DAP3 18 +#define TEGRA20_DAS_DAP_SEL_DAP4 19 +#define TEGRA20_DAS_DAP_SEL_DAP5 20 + +/* Register TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL */ +#define TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL 0x40 +#define TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL_COUNT 3 +#define TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL_STRIDE 4 +#define TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA2_SEL_P 28 +#define TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA2_SEL_S 4 +#define TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA1_SEL_P 24 +#define TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA1_SEL_S 4 +#define TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_CLK_SEL_P 0 +#define TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_CLK_SEL_S 4 + +/* + * Values for: + * TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA2_SEL + * TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA1_SEL + * TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_CLK_SEL + */ +#define TEGRA20_DAS_DAC_SEL_DAP1 0 +#define TEGRA20_DAS_DAC_SEL_DAP2 1 +#define TEGRA20_DAS_DAC_SEL_DAP3 2 +#define TEGRA20_DAS_DAC_SEL_DAP4 3 +#define TEGRA20_DAS_DAC_SEL_DAP5 4 + +/* + * Names/IDs of the DACs/DAPs. + */ + +#define TEGRA20_DAS_DAP_ID_1 0 +#define TEGRA20_DAS_DAP_ID_2 1 +#define TEGRA20_DAS_DAP_ID_3 2 +#define TEGRA20_DAS_DAP_ID_4 3 +#define TEGRA20_DAS_DAP_ID_5 4 + +#define TEGRA20_DAS_DAC_ID_1 0 +#define TEGRA20_DAS_DAC_ID_2 1 +#define TEGRA20_DAS_DAC_ID_3 2 + +struct tegra20_das { + struct device *dev; + struct regmap *regmap; +}; + +/* + * Terminology: + * DAS: Digital audio switch (HW module controlled by this driver) + * DAP: Digital audio port (port/pins on Tegra device) + * DAC: Digital audio controller (e.g. I2S or AC97 controller elsewhere) + * + * The Tegra DAS is a mux/cross-bar which can connect each DAP to a specific + * DAC, or another DAP. When DAPs are connected, one must be the master and + * one the slave. Each DAC allows selection of a specific DAP for input, to + * cater for the case where N DAPs are connected to 1 DAC for broadcast + * output. + * + * This driver is dumb; no attempt is made to ensure that a valid routing + * configuration is programmed. + */ + +/* + * Connect a DAP to to a DAC + * dap_id: DAP to connect: TEGRA20_DAS_DAP_ID_* + * dac_sel: DAC to connect to: TEGRA20_DAS_DAP_SEL_DAC* + */ +extern int tegra20_das_connect_dap_to_dac(int dap_id, int dac_sel); + +/* + * Connect a DAP to to another DAP + * dap_id: DAP to connect: TEGRA20_DAS_DAP_ID_* + * other_dap_sel: DAP to connect to: TEGRA20_DAS_DAP_SEL_DAP* + * master: Is this DAP the master (1) or slave (0) + * sdata1rx: Is this DAP's SDATA1 pin RX (1) or TX (0) + * sdata2rx: Is this DAP's SDATA2 pin RX (1) or TX (0) + */ +extern int tegra20_das_connect_dap_to_dap(int dap_id, int other_dap_sel, + int master, int sdata1rx, + int sdata2rx); + +/* + * Connect a DAC's input to a DAP + * (DAC outputs are selected by the DAP) + * dac_id: DAC ID to connect: TEGRA20_DAS_DAC_ID_* + * dap_sel: DAP to receive input from: TEGRA20_DAS_DAC_SEL_DAP* + */ +extern int tegra20_das_connect_dac_to_dap(int dac_id, int dap_sel); + +#endif diff --git a/sound/soc/tegra/tegra20_i2s.c b/sound/soc/tegra/tegra20_i2s.c new file mode 100644 index 00000000000..0c7af63d444 --- /dev/null +++ b/sound/soc/tegra/tegra20_i2s.c @@ -0,0 +1,494 @@ +/* + * tegra20_i2s.c - Tegra20 I2S driver + * + * Author: Stephen Warren <swarren@nvidia.com> + * Copyright (C) 2010,2012 - NVIDIA, Inc. + * + * Based on code copyright/by: + * + * Copyright (c) 2009-2010, NVIDIA Corporation. + * Scott Peterson <speterson@nvidia.com> + * + * Copyright (C) 2010 Google, Inc. + * Iliyan Malchev <malchev@google.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. + * + * 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/clk.h> +#include <linux/device.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <linux/slab.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> + +#include "tegra20_i2s.h" + +#define DRV_NAME "tegra20-i2s" + +static inline void tegra20_i2s_write(struct tegra20_i2s *i2s, u32 reg, u32 val) +{ + regmap_write(i2s->regmap, reg, val); +} + +static inline u32 tegra20_i2s_read(struct tegra20_i2s *i2s, u32 reg) +{ + u32 val; + regmap_read(i2s->regmap, reg, &val); + return val; +} + +static int tegra20_i2s_runtime_suspend(struct device *dev) +{ + struct tegra20_i2s *i2s = dev_get_drvdata(dev); + + clk_disable(i2s->clk_i2s); + + return 0; +} + +static int tegra20_i2s_runtime_resume(struct device *dev) +{ + struct tegra20_i2s *i2s = dev_get_drvdata(dev); + int ret; + + ret = clk_enable(i2s->clk_i2s); + if (ret) { + dev_err(dev, "clk_enable failed: %d\n", ret); + return ret; + } + + return 0; +} + +static int tegra20_i2s_set_fmt(struct snd_soc_dai *dai, + unsigned int fmt) +{ + struct tegra20_i2s *i2s = snd_soc_dai_get_drvdata(dai); + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + default: + return -EINVAL; + } + + i2s->reg_ctrl &= ~TEGRA20_I2S_CTRL_MASTER_ENABLE; + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + i2s->reg_ctrl |= TEGRA20_I2S_CTRL_MASTER_ENABLE; + break; + case SND_SOC_DAIFMT_CBM_CFM: + break; + default: + return -EINVAL; + } + + i2s->reg_ctrl &= ~(TEGRA20_I2S_CTRL_BIT_FORMAT_MASK | + TEGRA20_I2S_CTRL_LRCK_MASK); + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_A: + i2s->reg_ctrl |= TEGRA20_I2S_CTRL_BIT_FORMAT_DSP; + i2s->reg_ctrl |= TEGRA20_I2S_CTRL_LRCK_L_LOW; + break; + case SND_SOC_DAIFMT_DSP_B: + i2s->reg_ctrl |= TEGRA20_I2S_CTRL_BIT_FORMAT_DSP; + i2s->reg_ctrl |= TEGRA20_I2S_CTRL_LRCK_R_LOW; + break; + case SND_SOC_DAIFMT_I2S: + i2s->reg_ctrl |= TEGRA20_I2S_CTRL_BIT_FORMAT_I2S; + i2s->reg_ctrl |= TEGRA20_I2S_CTRL_LRCK_L_LOW; + break; + case SND_SOC_DAIFMT_RIGHT_J: + i2s->reg_ctrl |= TEGRA20_I2S_CTRL_BIT_FORMAT_RJM; + i2s->reg_ctrl |= TEGRA20_I2S_CTRL_LRCK_L_LOW; + break; + case SND_SOC_DAIFMT_LEFT_J: + i2s->reg_ctrl |= TEGRA20_I2S_CTRL_BIT_FORMAT_LJM; + i2s->reg_ctrl |= TEGRA20_I2S_CTRL_LRCK_L_LOW; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int tegra20_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct device *dev = substream->pcm->card->dev; + struct tegra20_i2s *i2s = snd_soc_dai_get_drvdata(dai); + u32 reg; + int ret, sample_size, srate, i2sclock, bitcnt; + + i2s->reg_ctrl &= ~TEGRA20_I2S_CTRL_BIT_SIZE_MASK; + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + i2s->reg_ctrl |= TEGRA20_I2S_CTRL_BIT_SIZE_16; + sample_size = 16; + break; + case SNDRV_PCM_FORMAT_S24_LE: + i2s->reg_ctrl |= TEGRA20_I2S_CTRL_BIT_SIZE_24; + sample_size = 24; + break; + case SNDRV_PCM_FORMAT_S32_LE: + i2s->reg_ctrl |= TEGRA20_I2S_CTRL_BIT_SIZE_32; + sample_size = 32; + break; + default: + return -EINVAL; + } + + srate = params_rate(params); + + /* Final "* 2" required by Tegra hardware */ + i2sclock = srate * params_channels(params) * sample_size * 2; + + ret = clk_set_rate(i2s->clk_i2s, i2sclock); + if (ret) { + dev_err(dev, "Can't set I2S clock rate: %d\n", ret); + return ret; + } + + bitcnt = (i2sclock / (2 * srate)) - 1; + if (bitcnt < 0 || bitcnt > TEGRA20_I2S_TIMING_CHANNEL_BIT_COUNT_MASK_US) + return -EINVAL; + reg = bitcnt << TEGRA20_I2S_TIMING_CHANNEL_BIT_COUNT_SHIFT; + + if (i2sclock % (2 * srate)) + reg |= TEGRA20_I2S_TIMING_NON_SYM_ENABLE; + + tegra20_i2s_write(i2s, TEGRA20_I2S_TIMING, reg); + + tegra20_i2s_write(i2s, TEGRA20_I2S_FIFO_SCR, + TEGRA20_I2S_FIFO_SCR_FIFO2_ATN_LVL_FOUR_SLOTS | + TEGRA20_I2S_FIFO_SCR_FIFO1_ATN_LVL_FOUR_SLOTS); + + return 0; +} + +static void tegra20_i2s_start_playback(struct tegra20_i2s *i2s) +{ + i2s->reg_ctrl |= TEGRA20_I2S_CTRL_FIFO1_ENABLE; + tegra20_i2s_write(i2s, TEGRA20_I2S_CTRL, i2s->reg_ctrl); +} + +static void tegra20_i2s_stop_playback(struct tegra20_i2s *i2s) +{ + i2s->reg_ctrl &= ~TEGRA20_I2S_CTRL_FIFO1_ENABLE; + tegra20_i2s_write(i2s, TEGRA20_I2S_CTRL, i2s->reg_ctrl); +} + +static void tegra20_i2s_start_capture(struct tegra20_i2s *i2s) +{ + i2s->reg_ctrl |= TEGRA20_I2S_CTRL_FIFO2_ENABLE; + tegra20_i2s_write(i2s, TEGRA20_I2S_CTRL, i2s->reg_ctrl); +} + +static void tegra20_i2s_stop_capture(struct tegra20_i2s *i2s) +{ + i2s->reg_ctrl &= ~TEGRA20_I2S_CTRL_FIFO2_ENABLE; + tegra20_i2s_write(i2s, TEGRA20_I2S_CTRL, i2s->reg_ctrl); +} + +static int tegra20_i2s_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct tegra20_i2s *i2s = snd_soc_dai_get_drvdata(dai); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + case SNDRV_PCM_TRIGGER_RESUME: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + tegra20_i2s_start_playback(i2s); + else + tegra20_i2s_start_capture(i2s); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_SUSPEND: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + tegra20_i2s_stop_playback(i2s); + else + tegra20_i2s_stop_capture(i2s); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int tegra20_i2s_probe(struct snd_soc_dai *dai) +{ + struct tegra20_i2s *i2s = snd_soc_dai_get_drvdata(dai); + + dai->capture_dma_data = &i2s->capture_dma_data; + dai->playback_dma_data = &i2s->playback_dma_data; + + return 0; +} + +static const struct snd_soc_dai_ops tegra20_i2s_dai_ops = { + .set_fmt = tegra20_i2s_set_fmt, + .hw_params = tegra20_i2s_hw_params, + .trigger = tegra20_i2s_trigger, +}; + +static const struct snd_soc_dai_driver tegra20_i2s_dai_template = { + .probe = tegra20_i2s_probe, + .playback = { + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &tegra20_i2s_dai_ops, + .symmetric_rates = 1, +}; + +static bool tegra20_i2s_wr_rd_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TEGRA20_I2S_CTRL: + case TEGRA20_I2S_STATUS: + case TEGRA20_I2S_TIMING: + case TEGRA20_I2S_FIFO_SCR: + case TEGRA20_I2S_PCM_CTRL: + case TEGRA20_I2S_NW_CTRL: + case TEGRA20_I2S_TDM_CTRL: + case TEGRA20_I2S_TDM_TX_RX_CTRL: + case TEGRA20_I2S_FIFO1: + case TEGRA20_I2S_FIFO2: + return true; + default: + return false; + }; +} + +static bool tegra20_i2s_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TEGRA20_I2S_STATUS: + case TEGRA20_I2S_FIFO_SCR: + case TEGRA20_I2S_FIFO1: + case TEGRA20_I2S_FIFO2: + return true; + default: + return false; + }; +} + +static bool tegra20_i2s_precious_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TEGRA20_I2S_FIFO1: + case TEGRA20_I2S_FIFO2: + return true; + default: + return false; + }; +} + +static const struct regmap_config tegra20_i2s_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = TEGRA20_I2S_FIFO2, + .writeable_reg = tegra20_i2s_wr_rd_reg, + .readable_reg = tegra20_i2s_wr_rd_reg, + .volatile_reg = tegra20_i2s_volatile_reg, + .precious_reg = tegra20_i2s_precious_reg, + .cache_type = REGCACHE_RBTREE, +}; + +static __devinit int tegra20_i2s_platform_probe(struct platform_device *pdev) +{ + struct tegra20_i2s *i2s; + struct resource *mem, *memregion, *dmareq; + u32 of_dma[2]; + u32 dma_ch; + void __iomem *regs; + int ret; + + i2s = devm_kzalloc(&pdev->dev, sizeof(struct tegra20_i2s), GFP_KERNEL); + if (!i2s) { + dev_err(&pdev->dev, "Can't allocate tegra20_i2s\n"); + ret = -ENOMEM; + goto err; + } + dev_set_drvdata(&pdev->dev, i2s); + + i2s->dai = tegra20_i2s_dai_template; + i2s->dai.name = dev_name(&pdev->dev); + + i2s->clk_i2s = clk_get(&pdev->dev, NULL); + if (IS_ERR(i2s->clk_i2s)) { + dev_err(&pdev->dev, "Can't retrieve i2s clock\n"); + ret = PTR_ERR(i2s->clk_i2s); + goto err; + } + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!mem) { + dev_err(&pdev->dev, "No memory resource\n"); + ret = -ENODEV; + goto err_clk_put; + } + + dmareq = platform_get_resource(pdev, IORESOURCE_DMA, 0); + if (!dmareq) { + if (of_property_read_u32_array(pdev->dev.of_node, + "nvidia,dma-request-selector", + of_dma, 2) < 0) { + dev_err(&pdev->dev, "No DMA resource\n"); + ret = -ENODEV; + goto err_clk_put; + } + dma_ch = of_dma[1]; + } else { + dma_ch = dmareq->start; + } + + memregion = devm_request_mem_region(&pdev->dev, mem->start, + resource_size(mem), DRV_NAME); + if (!memregion) { + dev_err(&pdev->dev, "Memory region already claimed\n"); + ret = -EBUSY; + goto err_clk_put; + } + + regs = devm_ioremap(&pdev->dev, mem->start, resource_size(mem)); + if (!regs) { + dev_err(&pdev->dev, "ioremap failed\n"); + ret = -ENOMEM; + goto err_clk_put; + } + + i2s->regmap = devm_regmap_init_mmio(&pdev->dev, regs, + &tegra20_i2s_regmap_config); + if (IS_ERR(i2s->regmap)) { + dev_err(&pdev->dev, "regmap init failed\n"); + ret = PTR_ERR(i2s->regmap); + goto err_clk_put; + } + + i2s->capture_dma_data.addr = mem->start + TEGRA20_I2S_FIFO2; + i2s->capture_dma_data.wrap = 4; + i2s->capture_dma_data.width = 32; + i2s->capture_dma_data.req_sel = dma_ch; + + i2s->playback_dma_data.addr = mem->start + TEGRA20_I2S_FIFO1; + i2s->playback_dma_data.wrap = 4; + i2s->playback_dma_data.width = 32; + i2s->playback_dma_data.req_sel = dma_ch; + + i2s->reg_ctrl = TEGRA20_I2S_CTRL_FIFO_FORMAT_PACKED; + + pm_runtime_enable(&pdev->dev); + if (!pm_runtime_enabled(&pdev->dev)) { + ret = tegra20_i2s_runtime_resume(&pdev->dev); + if (ret) + goto err_pm_disable; + } + + ret = snd_soc_register_dai(&pdev->dev, &i2s->dai); + if (ret) { + dev_err(&pdev->dev, "Could not register DAI: %d\n", ret); + ret = -ENOMEM; + goto err_suspend; + } + + ret = tegra_pcm_platform_register(&pdev->dev); + if (ret) { + dev_err(&pdev->dev, "Could not register PCM: %d\n", ret); + goto err_unregister_dai; + } + + return 0; + +err_unregister_dai: + snd_soc_unregister_dai(&pdev->dev); +err_suspend: + if (!pm_runtime_status_suspended(&pdev->dev)) + tegra20_i2s_runtime_suspend(&pdev->dev); +err_pm_disable: + pm_runtime_disable(&pdev->dev); +err_clk_put: + clk_put(i2s->clk_i2s); +err: + return ret; +} + +static int __devexit tegra20_i2s_platform_remove(struct platform_device *pdev) +{ + struct tegra20_i2s *i2s = dev_get_drvdata(&pdev->dev); + + pm_runtime_disable(&pdev->dev); + if (!pm_runtime_status_suspended(&pdev->dev)) + tegra20_i2s_runtime_suspend(&pdev->dev); + + tegra_pcm_platform_unregister(&pdev->dev); + snd_soc_unregister_dai(&pdev->dev); + + clk_put(i2s->clk_i2s); + + return 0; +} + +static const struct of_device_id tegra20_i2s_of_match[] __devinitconst = { + { .compatible = "nvidia,tegra20-i2s", }, + {}, +}; + +static const struct dev_pm_ops tegra20_i2s_pm_ops __devinitconst = { + SET_RUNTIME_PM_OPS(tegra20_i2s_runtime_suspend, + tegra20_i2s_runtime_resume, NULL) +}; + +static struct platform_driver tegra20_i2s_driver = { + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + .of_match_table = tegra20_i2s_of_match, + .pm = &tegra20_i2s_pm_ops, + }, + .probe = tegra20_i2s_platform_probe, + .remove = __devexit_p(tegra20_i2s_platform_remove), +}; +module_platform_driver(tegra20_i2s_driver); + +MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>"); +MODULE_DESCRIPTION("Tegra20 I2S ASoC driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRV_NAME); +MODULE_DEVICE_TABLE(of, tegra20_i2s_of_match); diff --git a/sound/soc/tegra/tegra20_i2s.h b/sound/soc/tegra/tegra20_i2s.h new file mode 100644 index 00000000000..a57efc6a597 --- /dev/null +++ b/sound/soc/tegra/tegra20_i2s.h @@ -0,0 +1,164 @@ +/* + * tegra20_i2s.h - Definitions for Tegra20 I2S driver + * + * Author: Stephen Warren <swarren@nvidia.com> + * Copyright (C) 2010,2012 - NVIDIA, Inc. + * + * Based on code copyright/by: + * + * Copyright (c) 2009-2010, NVIDIA Corporation. + * Scott Peterson <speterson@nvidia.com> + * + * Copyright (C) 2010 Google, Inc. + * Iliyan Malchev <malchev@google.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. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __TEGRA20_I2S_H__ +#define __TEGRA20_I2S_H__ + +#include "tegra_pcm.h" + +/* Register offsets from TEGRA20_I2S1_BASE and TEGRA20_I2S2_BASE */ + +#define TEGRA20_I2S_CTRL 0x00 +#define TEGRA20_I2S_STATUS 0x04 +#define TEGRA20_I2S_TIMING 0x08 +#define TEGRA20_I2S_FIFO_SCR 0x0c +#define TEGRA20_I2S_PCM_CTRL 0x10 +#define TEGRA20_I2S_NW_CTRL 0x14 +#define TEGRA20_I2S_TDM_CTRL 0x20 +#define TEGRA20_I2S_TDM_TX_RX_CTRL 0x24 +#define TEGRA20_I2S_FIFO1 0x40 +#define TEGRA20_I2S_FIFO2 0x80 + +/* Fields in TEGRA20_I2S_CTRL */ + +#define TEGRA20_I2S_CTRL_FIFO2_TX_ENABLE (1 << 30) +#define TEGRA20_I2S_CTRL_FIFO1_ENABLE (1 << 29) +#define TEGRA20_I2S_CTRL_FIFO2_ENABLE (1 << 28) +#define TEGRA20_I2S_CTRL_FIFO1_RX_ENABLE (1 << 27) +#define TEGRA20_I2S_CTRL_FIFO_LPBK_ENABLE (1 << 26) +#define TEGRA20_I2S_CTRL_MASTER_ENABLE (1 << 25) + +#define TEGRA20_I2S_LRCK_LEFT_LOW 0 +#define TEGRA20_I2S_LRCK_RIGHT_LOW 1 + +#define TEGRA20_I2S_CTRL_LRCK_SHIFT 24 +#define TEGRA20_I2S_CTRL_LRCK_MASK (1 << TEGRA20_I2S_CTRL_LRCK_SHIFT) +#define TEGRA20_I2S_CTRL_LRCK_L_LOW (TEGRA20_I2S_LRCK_LEFT_LOW << TEGRA20_I2S_CTRL_LRCK_SHIFT) +#define TEGRA20_I2S_CTRL_LRCK_R_LOW (TEGRA20_I2S_LRCK_RIGHT_LOW << TEGRA20_I2S_CTRL_LRCK_SHIFT) + +#define TEGRA20_I2S_BIT_FORMAT_I2S 0 +#define TEGRA20_I2S_BIT_FORMAT_RJM 1 +#define TEGRA20_I2S_BIT_FORMAT_LJM 2 +#define TEGRA20_I2S_BIT_FORMAT_DSP 3 + +#define TEGRA20_I2S_CTRL_BIT_FORMAT_SHIFT 10 +#define TEGRA20_I2S_CTRL_BIT_FORMAT_MASK (3 << TEGRA20_I2S_CTRL_BIT_FORMAT_SHIFT) +#define TEGRA20_I2S_CTRL_BIT_FORMAT_I2S (TEGRA20_I2S_BIT_FORMAT_I2S << TEGRA20_I2S_CTRL_BIT_FORMAT_SHIFT) +#define TEGRA20_I2S_CTRL_BIT_FORMAT_RJM (TEGRA20_I2S_BIT_FORMAT_RJM << TEGRA20_I2S_CTRL_BIT_FORMAT_SHIFT) +#define TEGRA20_I2S_CTRL_BIT_FORMAT_LJM (TEGRA20_I2S_BIT_FORMAT_LJM << TEGRA20_I2S_CTRL_BIT_FORMAT_SHIFT) +#define TEGRA20_I2S_CTRL_BIT_FORMAT_DSP (TEGRA20_I2S_BIT_FORMAT_DSP << TEGRA20_I2S_CTRL_BIT_FORMAT_SHIFT) + +#define TEGRA20_I2S_BIT_SIZE_16 0 +#define TEGRA20_I2S_BIT_SIZE_20 1 +#define TEGRA20_I2S_BIT_SIZE_24 2 +#define TEGRA20_I2S_BIT_SIZE_32 3 + +#define TEGRA20_I2S_CTRL_BIT_SIZE_SHIFT 8 +#define TEGRA20_I2S_CTRL_BIT_SIZE_MASK (3 << TEGRA20_I2S_CTRL_BIT_SIZE_SHIFT) +#define TEGRA20_I2S_CTRL_BIT_SIZE_16 (TEGRA20_I2S_BIT_SIZE_16 << TEGRA20_I2S_CTRL_BIT_SIZE_SHIFT) +#define TEGRA20_I2S_CTRL_BIT_SIZE_20 (TEGRA20_I2S_BIT_SIZE_20 << TEGRA20_I2S_CTRL_BIT_SIZE_SHIFT) +#define TEGRA20_I2S_CTRL_BIT_SIZE_24 (TEGRA20_I2S_BIT_SIZE_24 << TEGRA20_I2S_CTRL_BIT_SIZE_SHIFT) +#define TEGRA20_I2S_CTRL_BIT_SIZE_32 (TEGRA20_I2S_BIT_SIZE_32 << TEGRA20_I2S_CTRL_BIT_SIZE_SHIFT) + +#define TEGRA20_I2S_FIFO_16_LSB 0 +#define TEGRA20_I2S_FIFO_20_LSB 1 +#define TEGRA20_I2S_FIFO_24_LSB 2 +#define TEGRA20_I2S_FIFO_32 3 +#define TEGRA20_I2S_FIFO_PACKED 7 + +#define TEGRA20_I2S_CTRL_FIFO_FORMAT_SHIFT 4 +#define TEGRA20_I2S_CTRL_FIFO_FORMAT_MASK (7 << TEGRA20_I2S_CTRL_FIFO_FORMAT_SHIFT) +#define TEGRA20_I2S_CTRL_FIFO_FORMAT_16_LSB (TEGRA20_I2S_FIFO_16_LSB << TEGRA20_I2S_CTRL_FIFO_FORMAT_SHIFT) +#define TEGRA20_I2S_CTRL_FIFO_FORMAT_20_LSB (TEGRA20_I2S_FIFO_20_LSB << TEGRA20_I2S_CTRL_FIFO_FORMAT_SHIFT) +#define TEGRA20_I2S_CTRL_FIFO_FORMAT_24_LSB (TEGRA20_I2S_FIFO_24_LSB << TEGRA20_I2S_CTRL_FIFO_FORMAT_SHIFT) +#define TEGRA20_I2S_CTRL_FIFO_FORMAT_32 (TEGRA20_I2S_FIFO_32 << TEGRA20_I2S_CTRL_FIFO_FORMAT_SHIFT) +#define TEGRA20_I2S_CTRL_FIFO_FORMAT_PACKED (TEGRA20_I2S_FIFO_PACKED << TEGRA20_I2S_CTRL_FIFO_FORMAT_SHIFT) + +#define TEGRA20_I2S_CTRL_IE_FIFO1_ERR (1 << 3) +#define TEGRA20_I2S_CTRL_IE_FIFO2_ERR (1 << 2) +#define TEGRA20_I2S_CTRL_QE_FIFO1 (1 << 1) +#define TEGRA20_I2S_CTRL_QE_FIFO2 (1 << 0) + +/* Fields in TEGRA20_I2S_STATUS */ + +#define TEGRA20_I2S_STATUS_FIFO1_RDY (1 << 31) +#define TEGRA20_I2S_STATUS_FIFO2_RDY (1 << 30) +#define TEGRA20_I2S_STATUS_FIFO1_BSY (1 << 29) +#define TEGRA20_I2S_STATUS_FIFO2_BSY (1 << 28) +#define TEGRA20_I2S_STATUS_FIFO1_ERR (1 << 3) +#define TEGRA20_I2S_STATUS_FIFO2_ERR (1 << 2) +#define TEGRA20_I2S_STATUS_QS_FIFO1 (1 << 1) +#define TEGRA20_I2S_STATUS_QS_FIFO2 (1 << 0) + +/* Fields in TEGRA20_I2S_TIMING */ + +#define TEGRA20_I2S_TIMING_NON_SYM_ENABLE (1 << 12) +#define TEGRA20_I2S_TIMING_CHANNEL_BIT_COUNT_SHIFT 0 +#define TEGRA20_I2S_TIMING_CHANNEL_BIT_COUNT_MASK_US 0x7fff +#define TEGRA20_I2S_TIMING_CHANNEL_BIT_COUNT_MASK (TEGRA20_I2S_TIMING_CHANNEL_BIT_COUNT_MASK_US << TEGRA20_I2S_TIMING_CHANNEL_BIT_COUNT_SHIFT) + +/* Fields in TEGRA20_I2S_FIFO_SCR */ + +#define TEGRA20_I2S_FIFO_SCR_FIFO2_FULL_EMPTY_COUNT_SHIFT 24 +#define TEGRA20_I2S_FIFO_SCR_FIFO1_FULL_EMPTY_COUNT_SHIFT 16 +#define TEGRA20_I2S_FIFO_SCR_FIFO_FULL_EMPTY_COUNT_MASK 0x3f + +#define TEGRA20_I2S_FIFO_SCR_FIFO2_CLR (1 << 12) +#define TEGRA20_I2S_FIFO_SCR_FIFO1_CLR (1 << 8) + +#define TEGRA20_I2S_FIFO_ATN_LVL_ONE_SLOT 0 +#define TEGRA20_I2S_FIFO_ATN_LVL_FOUR_SLOTS 1 +#define TEGRA20_I2S_FIFO_ATN_LVL_EIGHT_SLOTS 2 +#define TEGRA20_I2S_FIFO_ATN_LVL_TWELVE_SLOTS 3 + +#define TEGRA20_I2S_FIFO_SCR_FIFO2_ATN_LVL_SHIFT 4 +#define TEGRA20_I2S_FIFO_SCR_FIFO2_ATN_LVL_MASK (3 << TEGRA20_I2S_FIFO_SCR_FIFO2_ATN_LVL_SHIFT) +#define TEGRA20_I2S_FIFO_SCR_FIFO2_ATN_LVL_ONE_SLOT (TEGRA20_I2S_FIFO_ATN_LVL_ONE_SLOT << TEGRA20_I2S_FIFO_SCR_FIFO2_ATN_LVL_SHIFT) +#define TEGRA20_I2S_FIFO_SCR_FIFO2_ATN_LVL_FOUR_SLOTS (TEGRA20_I2S_FIFO_ATN_LVL_FOUR_SLOTS << TEGRA20_I2S_FIFO_SCR_FIFO2_ATN_LVL_SHIFT) +#define TEGRA20_I2S_FIFO_SCR_FIFO2_ATN_LVL_EIGHT_SLOTS (TEGRA20_I2S_FIFO_ATN_LVL_EIGHT_SLOTS << TEGRA20_I2S_FIFO_SCR_FIFO2_ATN_LVL_SHIFT) +#define TEGRA20_I2S_FIFO_SCR_FIFO2_ATN_LVL_TWELVE_SLOTS (TEGRA20_I2S_FIFO_ATN_LVL_TWELVE_SLOTS << TEGRA20_I2S_FIFO_SCR_FIFO2_ATN_LVL_SHIFT) + +#define TEGRA20_I2S_FIFO_SCR_FIFO1_ATN_LVL_SHIFT 0 +#define TEGRA20_I2S_FIFO_SCR_FIFO1_ATN_LVL_MASK (3 << TEGRA20_I2S_FIFO_SCR_FIFO1_ATN_LVL_SHIFT) +#define TEGRA20_I2S_FIFO_SCR_FIFO1_ATN_LVL_ONE_SLOT (TEGRA20_I2S_FIFO_ATN_LVL_ONE_SLOT << TEGRA20_I2S_FIFO_SCR_FIFO1_ATN_LVL_SHIFT) +#define TEGRA20_I2S_FIFO_SCR_FIFO1_ATN_LVL_FOUR_SLOTS (TEGRA20_I2S_FIFO_ATN_LVL_FOUR_SLOTS << TEGRA20_I2S_FIFO_SCR_FIFO1_ATN_LVL_SHIFT) +#define TEGRA20_I2S_FIFO_SCR_FIFO1_ATN_LVL_EIGHT_SLOTS (TEGRA20_I2S_FIFO_ATN_LVL_EIGHT_SLOTS << TEGRA20_I2S_FIFO_SCR_FIFO1_ATN_LVL_SHIFT) +#define TEGRA20_I2S_FIFO_SCR_FIFO1_ATN_LVL_TWELVE_SLOTS (TEGRA20_I2S_FIFO_ATN_LVL_TWELVE_SLOTS << TEGRA20_I2S_FIFO_SCR_FIFO1_ATN_LVL_SHIFT) + +struct tegra20_i2s { + struct snd_soc_dai_driver dai; + struct clk *clk_i2s; + struct tegra_pcm_dma_params capture_dma_data; + struct tegra_pcm_dma_params playback_dma_data; + struct regmap *regmap; + u32 reg_ctrl; +}; + +#endif diff --git a/sound/soc/tegra/tegra20_spdif.c b/sound/soc/tegra/tegra20_spdif.c new file mode 100644 index 00000000000..f9b57418bd0 --- /dev/null +++ b/sound/soc/tegra/tegra20_spdif.c @@ -0,0 +1,404 @@ +/* + * tegra20_spdif.c - Tegra20 SPDIF driver + * + * Author: Stephen Warren <swarren@nvidia.com> + * Copyright (C) 2011-2012 - NVIDIA, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * 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/clk.h> +#include <linux/device.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <linux/slab.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> + +#include "tegra20_spdif.h" + +#define DRV_NAME "tegra20-spdif" + +static inline void tegra20_spdif_write(struct tegra20_spdif *spdif, u32 reg, + u32 val) +{ + regmap_write(spdif->regmap, reg, val); +} + +static inline u32 tegra20_spdif_read(struct tegra20_spdif *spdif, u32 reg) +{ + u32 val; + regmap_read(spdif->regmap, reg, &val); + return val; +} + +static int tegra20_spdif_runtime_suspend(struct device *dev) +{ + struct tegra20_spdif *spdif = dev_get_drvdata(dev); + + clk_disable(spdif->clk_spdif_out); + + return 0; +} + +static int tegra20_spdif_runtime_resume(struct device *dev) +{ + struct tegra20_spdif *spdif = dev_get_drvdata(dev); + int ret; + + ret = clk_enable(spdif->clk_spdif_out); + if (ret) { + dev_err(dev, "clk_enable failed: %d\n", ret); + return ret; + } + + return 0; +} + +static int tegra20_spdif_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct device *dev = substream->pcm->card->dev; + struct tegra20_spdif *spdif = snd_soc_dai_get_drvdata(dai); + int ret, spdifclock; + + spdif->reg_ctrl &= ~TEGRA20_SPDIF_CTRL_PACK; + spdif->reg_ctrl &= ~TEGRA20_SPDIF_CTRL_BIT_MODE_MASK; + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + spdif->reg_ctrl |= TEGRA20_SPDIF_CTRL_PACK; + spdif->reg_ctrl |= TEGRA20_SPDIF_CTRL_BIT_MODE_16BIT; + break; + default: + return -EINVAL; + } + + switch (params_rate(params)) { + case 32000: + spdifclock = 4096000; + break; + case 44100: + spdifclock = 5644800; + break; + case 48000: + spdifclock = 6144000; + break; + case 88200: + spdifclock = 11289600; + break; + case 96000: + spdifclock = 12288000; + break; + case 176400: + spdifclock = 22579200; + break; + case 192000: + spdifclock = 24576000; + break; + default: + return -EINVAL; + } + + ret = clk_set_rate(spdif->clk_spdif_out, spdifclock); + if (ret) { + dev_err(dev, "Can't set SPDIF clock rate: %d\n", ret); + return ret; + } + + return 0; +} + +static void tegra20_spdif_start_playback(struct tegra20_spdif *spdif) +{ + spdif->reg_ctrl |= TEGRA20_SPDIF_CTRL_TX_EN; + tegra20_spdif_write(spdif, TEGRA20_SPDIF_CTRL, spdif->reg_ctrl); +} + +static void tegra20_spdif_stop_playback(struct tegra20_spdif *spdif) +{ + spdif->reg_ctrl &= ~TEGRA20_SPDIF_CTRL_TX_EN; + tegra20_spdif_write(spdif, TEGRA20_SPDIF_CTRL, spdif->reg_ctrl); +} + +static int tegra20_spdif_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct tegra20_spdif *spdif = snd_soc_dai_get_drvdata(dai); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + case SNDRV_PCM_TRIGGER_RESUME: + tegra20_spdif_start_playback(spdif); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_SUSPEND: + tegra20_spdif_stop_playback(spdif); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int tegra20_spdif_probe(struct snd_soc_dai *dai) +{ + struct tegra20_spdif *spdif = snd_soc_dai_get_drvdata(dai); + + dai->capture_dma_data = NULL; + dai->playback_dma_data = &spdif->playback_dma_data; + + return 0; +} + +static const struct snd_soc_dai_ops tegra20_spdif_dai_ops = { + .hw_params = tegra20_spdif_hw_params, + .trigger = tegra20_spdif_trigger, +}; + +static struct snd_soc_dai_driver tegra20_spdif_dai = { + .name = DRV_NAME, + .probe = tegra20_spdif_probe, + .playback = { + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &tegra20_spdif_dai_ops, +}; + +static bool tegra20_spdif_wr_rd_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TEGRA20_SPDIF_CTRL: + case TEGRA20_SPDIF_STATUS: + case TEGRA20_SPDIF_STROBE_CTRL: + case TEGRA20_SPDIF_DATA_FIFO_CSR: + case TEGRA20_SPDIF_DATA_OUT: + case TEGRA20_SPDIF_DATA_IN: + case TEGRA20_SPDIF_CH_STA_RX_A: + case TEGRA20_SPDIF_CH_STA_RX_B: + case TEGRA20_SPDIF_CH_STA_RX_C: + case TEGRA20_SPDIF_CH_STA_RX_D: + case TEGRA20_SPDIF_CH_STA_RX_E: + case TEGRA20_SPDIF_CH_STA_RX_F: + case TEGRA20_SPDIF_CH_STA_TX_A: + case TEGRA20_SPDIF_CH_STA_TX_B: + case TEGRA20_SPDIF_CH_STA_TX_C: + case TEGRA20_SPDIF_CH_STA_TX_D: + case TEGRA20_SPDIF_CH_STA_TX_E: + case TEGRA20_SPDIF_CH_STA_TX_F: + case TEGRA20_SPDIF_USR_STA_RX_A: + case TEGRA20_SPDIF_USR_DAT_TX_A: + return true; + default: + return false; + }; +} + +static bool tegra20_spdif_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TEGRA20_SPDIF_STATUS: + case TEGRA20_SPDIF_DATA_FIFO_CSR: + case TEGRA20_SPDIF_DATA_OUT: + case TEGRA20_SPDIF_DATA_IN: + case TEGRA20_SPDIF_CH_STA_RX_A: + case TEGRA20_SPDIF_CH_STA_RX_B: + case TEGRA20_SPDIF_CH_STA_RX_C: + case TEGRA20_SPDIF_CH_STA_RX_D: + case TEGRA20_SPDIF_CH_STA_RX_E: + case TEGRA20_SPDIF_CH_STA_RX_F: + case TEGRA20_SPDIF_USR_STA_RX_A: + case TEGRA20_SPDIF_USR_DAT_TX_A: + return true; + default: + return false; + }; +} + +static bool tegra20_spdif_precious_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TEGRA20_SPDIF_DATA_OUT: + case TEGRA20_SPDIF_DATA_IN: + case TEGRA20_SPDIF_USR_STA_RX_A: + case TEGRA20_SPDIF_USR_DAT_TX_A: + return true; + default: + return false; + }; +} + +static const struct regmap_config tegra20_spdif_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = TEGRA20_SPDIF_USR_DAT_TX_A, + .writeable_reg = tegra20_spdif_wr_rd_reg, + .readable_reg = tegra20_spdif_wr_rd_reg, + .volatile_reg = tegra20_spdif_volatile_reg, + .precious_reg = tegra20_spdif_precious_reg, + .cache_type = REGCACHE_RBTREE, +}; + +static __devinit int tegra20_spdif_platform_probe(struct platform_device *pdev) +{ + struct tegra20_spdif *spdif; + struct resource *mem, *memregion, *dmareq; + void __iomem *regs; + int ret; + + spdif = devm_kzalloc(&pdev->dev, sizeof(struct tegra20_spdif), + GFP_KERNEL); + if (!spdif) { + dev_err(&pdev->dev, "Can't allocate tegra20_spdif\n"); + ret = -ENOMEM; + goto err; + } + dev_set_drvdata(&pdev->dev, spdif); + + spdif->clk_spdif_out = clk_get(&pdev->dev, "spdif_out"); + if (IS_ERR(spdif->clk_spdif_out)) { + pr_err("Can't retrieve spdif clock\n"); + ret = PTR_ERR(spdif->clk_spdif_out); + goto err; + } + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!mem) { + dev_err(&pdev->dev, "No memory resource\n"); + ret = -ENODEV; + goto err_clk_put; + } + + dmareq = platform_get_resource(pdev, IORESOURCE_DMA, 0); + if (!dmareq) { + dev_err(&pdev->dev, "No DMA resource\n"); + ret = -ENODEV; + goto err_clk_put; + } + + memregion = devm_request_mem_region(&pdev->dev, mem->start, + resource_size(mem), DRV_NAME); + if (!memregion) { + dev_err(&pdev->dev, "Memory region already claimed\n"); + ret = -EBUSY; + goto err_clk_put; + } + + regs = devm_ioremap(&pdev->dev, mem->start, resource_size(mem)); + if (!regs) { + dev_err(&pdev->dev, "ioremap failed\n"); + ret = -ENOMEM; + goto err_clk_put; + } + + spdif->regmap = devm_regmap_init_mmio(&pdev->dev, regs, + &tegra20_spdif_regmap_config); + if (IS_ERR(spdif->regmap)) { + dev_err(&pdev->dev, "regmap init failed\n"); + ret = PTR_ERR(spdif->regmap); + goto err_clk_put; + } + + spdif->playback_dma_data.addr = mem->start + TEGRA20_SPDIF_DATA_OUT; + spdif->playback_dma_data.wrap = 4; + spdif->playback_dma_data.width = 32; + spdif->playback_dma_data.req_sel = dmareq->start; + + pm_runtime_enable(&pdev->dev); + if (!pm_runtime_enabled(&pdev->dev)) { + ret = tegra20_spdif_runtime_resume(&pdev->dev); + if (ret) + goto err_pm_disable; + } + + ret = snd_soc_register_dai(&pdev->dev, &tegra20_spdif_dai); + if (ret) { + dev_err(&pdev->dev, "Could not register DAI: %d\n", ret); + ret = -ENOMEM; + goto err_suspend; + } + + ret = tegra_pcm_platform_register(&pdev->dev); + if (ret) { + dev_err(&pdev->dev, "Could not register PCM: %d\n", ret); + goto err_unregister_dai; + } + + return 0; + +err_unregister_dai: + snd_soc_unregister_dai(&pdev->dev); +err_suspend: + if (!pm_runtime_status_suspended(&pdev->dev)) + tegra20_spdif_runtime_suspend(&pdev->dev); +err_pm_disable: + pm_runtime_disable(&pdev->dev); +err_clk_put: + clk_put(spdif->clk_spdif_out); +err: + return ret; +} + +static int __devexit tegra20_spdif_platform_remove(struct platform_device *pdev) +{ + struct tegra20_spdif *spdif = dev_get_drvdata(&pdev->dev); + + pm_runtime_disable(&pdev->dev); + if (!pm_runtime_status_suspended(&pdev->dev)) + tegra20_spdif_runtime_suspend(&pdev->dev); + + tegra_pcm_platform_unregister(&pdev->dev); + snd_soc_unregister_dai(&pdev->dev); + + clk_put(spdif->clk_spdif_out); + + return 0; +} + +static const struct dev_pm_ops tegra20_spdif_pm_ops __devinitconst = { + SET_RUNTIME_PM_OPS(tegra20_spdif_runtime_suspend, + tegra20_spdif_runtime_resume, NULL) +}; + +static struct platform_driver tegra20_spdif_driver = { + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + .pm = &tegra20_spdif_pm_ops, + }, + .probe = tegra20_spdif_platform_probe, + .remove = __devexit_p(tegra20_spdif_platform_remove), +}; + +module_platform_driver(tegra20_spdif_driver); + +MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>"); +MODULE_DESCRIPTION("Tegra20 SPDIF ASoC driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRV_NAME); diff --git a/sound/soc/tegra/tegra20_spdif.h b/sound/soc/tegra/tegra20_spdif.h new file mode 100644 index 00000000000..ed756527efe --- /dev/null +++ b/sound/soc/tegra/tegra20_spdif.h @@ -0,0 +1,471 @@ +/* + * tegra20_spdif.h - Definitions for Tegra20 SPDIF driver + * + * Author: Stephen Warren <swarren@nvidia.com> + * Copyright (C) 2011 - NVIDIA, Inc. + * + * Based on code copyright/by: + * Copyright (c) 2008-2009, NVIDIA Corporation + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __TEGRA20_SPDIF_H__ +#define __TEGRA20_SPDIF_H__ + +#include "tegra_pcm.h" + +/* Offsets from TEGRA20_SPDIF_BASE */ + +#define TEGRA20_SPDIF_CTRL 0x0 +#define TEGRA20_SPDIF_STATUS 0x4 +#define TEGRA20_SPDIF_STROBE_CTRL 0x8 +#define TEGRA20_SPDIF_DATA_FIFO_CSR 0x0C +#define TEGRA20_SPDIF_DATA_OUT 0x40 +#define TEGRA20_SPDIF_DATA_IN 0x80 +#define TEGRA20_SPDIF_CH_STA_RX_A 0x100 +#define TEGRA20_SPDIF_CH_STA_RX_B 0x104 +#define TEGRA20_SPDIF_CH_STA_RX_C 0x108 +#define TEGRA20_SPDIF_CH_STA_RX_D 0x10C +#define TEGRA20_SPDIF_CH_STA_RX_E 0x110 +#define TEGRA20_SPDIF_CH_STA_RX_F 0x114 +#define TEGRA20_SPDIF_CH_STA_TX_A 0x140 +#define TEGRA20_SPDIF_CH_STA_TX_B 0x144 +#define TEGRA20_SPDIF_CH_STA_TX_C 0x148 +#define TEGRA20_SPDIF_CH_STA_TX_D 0x14C +#define TEGRA20_SPDIF_CH_STA_TX_E 0x150 +#define TEGRA20_SPDIF_CH_STA_TX_F 0x154 +#define TEGRA20_SPDIF_USR_STA_RX_A 0x180 +#define TEGRA20_SPDIF_USR_DAT_TX_A 0x1C0 + +/* Fields in TEGRA20_SPDIF_CTRL */ + +/* Start capturing from 0=right, 1=left channel */ +#define TEGRA20_SPDIF_CTRL_CAP_LC (1 << 30) + +/* SPDIF receiver(RX) enable */ +#define TEGRA20_SPDIF_CTRL_RX_EN (1 << 29) + +/* SPDIF Transmitter(TX) enable */ +#define TEGRA20_SPDIF_CTRL_TX_EN (1 << 28) + +/* Transmit Channel status */ +#define TEGRA20_SPDIF_CTRL_TC_EN (1 << 27) + +/* Transmit user Data */ +#define TEGRA20_SPDIF_CTRL_TU_EN (1 << 26) + +/* Interrupt on transmit error */ +#define TEGRA20_SPDIF_CTRL_IE_TXE (1 << 25) + +/* Interrupt on receive error */ +#define TEGRA20_SPDIF_CTRL_IE_RXE (1 << 24) + +/* Interrupt on invalid preamble */ +#define TEGRA20_SPDIF_CTRL_IE_P (1 << 23) + +/* Interrupt on "B" preamble */ +#define TEGRA20_SPDIF_CTRL_IE_B (1 << 22) + +/* Interrupt when block of channel status received */ +#define TEGRA20_SPDIF_CTRL_IE_C (1 << 21) + +/* Interrupt when a valid information unit (IU) is received */ +#define TEGRA20_SPDIF_CTRL_IE_U (1 << 20) + +/* Interrupt when RX user FIFO attention level is reached */ +#define TEGRA20_SPDIF_CTRL_QE_RU (1 << 19) + +/* Interrupt when TX user FIFO attention level is reached */ +#define TEGRA20_SPDIF_CTRL_QE_TU (1 << 18) + +/* Interrupt when RX data FIFO attention level is reached */ +#define TEGRA20_SPDIF_CTRL_QE_RX (1 << 17) + +/* Interrupt when TX data FIFO attention level is reached */ +#define TEGRA20_SPDIF_CTRL_QE_TX (1 << 16) + +/* Loopback test mode enable */ +#define TEGRA20_SPDIF_CTRL_LBK_EN (1 << 15) + +/* + * Pack data mode: + * 0 = Single data (16 bit needs to be padded to match the + * interface data bit size). + * 1 = Packeted left/right channel data into a single word. + */ +#define TEGRA20_SPDIF_CTRL_PACK (1 << 14) + +/* + * 00 = 16bit data + * 01 = 20bit data + * 10 = 24bit data + * 11 = raw data + */ +#define TEGRA20_SPDIF_BIT_MODE_16BIT 0 +#define TEGRA20_SPDIF_BIT_MODE_20BIT 1 +#define TEGRA20_SPDIF_BIT_MODE_24BIT 2 +#define TEGRA20_SPDIF_BIT_MODE_RAW 3 + +#define TEGRA20_SPDIF_CTRL_BIT_MODE_SHIFT 12 +#define TEGRA20_SPDIF_CTRL_BIT_MODE_MASK (3 << TEGRA20_SPDIF_CTRL_BIT_MODE_SHIFT) +#define TEGRA20_SPDIF_CTRL_BIT_MODE_16BIT (TEGRA20_SPDIF_BIT_MODE_16BIT << TEGRA20_SPDIF_CTRL_BIT_MODE_SHIFT) +#define TEGRA20_SPDIF_CTRL_BIT_MODE_20BIT (TEGRA20_SPDIF_BIT_MODE_20BIT << TEGRA20_SPDIF_CTRL_BIT_MODE_SHIFT) +#define TEGRA20_SPDIF_CTRL_BIT_MODE_24BIT (TEGRA20_SPDIF_BIT_MODE_24BIT << TEGRA20_SPDIF_CTRL_BIT_MODE_SHIFT) +#define TEGRA20_SPDIF_CTRL_BIT_MODE_RAW (TEGRA20_SPDIF_BIT_MODE_RAW << TEGRA20_SPDIF_CTRL_BIT_MODE_SHIFT) + +/* Fields in TEGRA20_SPDIF_STATUS */ + +/* + * Note: IS_P, IS_B, IS_C, and IS_U are sticky bits. Software must + * write a 1 to the corresponding bit location to clear the status. + */ + +/* + * Receiver(RX) shifter is busy receiving data. + * This bit is asserted when the receiver first locked onto the + * preamble of the data stream after RX_EN is asserted. This bit is + * deasserted when either, + * (a) the end of a frame is reached after RX_EN is deeasserted, or + * (b) the SPDIF data stream becomes inactive. + */ +#define TEGRA20_SPDIF_STATUS_RX_BSY (1 << 29) + +/* + * Transmitter(TX) shifter is busy transmitting data. + * This bit is asserted when TX_EN is asserted. + * This bit is deasserted when the end of a frame is reached after + * TX_EN is deasserted. + */ +#define TEGRA20_SPDIF_STATUS_TX_BSY (1 << 28) + +/* + * TX is busy shifting out channel status. + * This bit is asserted when both TX_EN and TC_EN are asserted and + * data from CH_STA_TX_A register is loaded into the internal shifter. + * This bit is deasserted when either, + * (a) the end of a frame is reached after TX_EN is deasserted, or + * (b) CH_STA_TX_F register is loaded into the internal shifter. + */ +#define TEGRA20_SPDIF_STATUS_TC_BSY (1 << 27) + +/* + * TX User data FIFO busy. + * This bit is asserted when TX_EN and TXU_EN are asserted and + * there's data in the TX user FIFO. This bit is deassert when either, + * (a) the end of a frame is reached after TX_EN is deasserted, or + * (b) there's no data left in the TX user FIFO. + */ +#define TEGRA20_SPDIF_STATUS_TU_BSY (1 << 26) + +/* TX FIFO Underrun error status */ +#define TEGRA20_SPDIF_STATUS_TX_ERR (1 << 25) + +/* RX FIFO Overrun error status */ +#define TEGRA20_SPDIF_STATUS_RX_ERR (1 << 24) + +/* Preamble status: 0=Preamble OK, 1=bad/missing preamble */ +#define TEGRA20_SPDIF_STATUS_IS_P (1 << 23) + +/* B-preamble detection status: 0=not detected, 1=B-preamble detected */ +#define TEGRA20_SPDIF_STATUS_IS_B (1 << 22) + +/* + * RX channel block data receive status: + * 0=entire block not recieved yet. + * 1=received entire block of channel status, + */ +#define TEGRA20_SPDIF_STATUS_IS_C (1 << 21) + +/* RX User Data Valid flag: 1=valid IU detected, 0 = no IU detected. */ +#define TEGRA20_SPDIF_STATUS_IS_U (1 << 20) + +/* + * RX User FIFO Status: + * 1=attention level reached, 0=attention level not reached. + */ +#define TEGRA20_SPDIF_STATUS_QS_RU (1 << 19) + +/* + * TX User FIFO Status: + * 1=attention level reached, 0=attention level not reached. + */ +#define TEGRA20_SPDIF_STATUS_QS_TU (1 << 18) + +/* + * RX Data FIFO Status: + * 1=attention level reached, 0=attention level not reached. + */ +#define TEGRA20_SPDIF_STATUS_QS_RX (1 << 17) + +/* + * TX Data FIFO Status: + * 1=attention level reached, 0=attention level not reached. + */ +#define TEGRA20_SPDIF_STATUS_QS_TX (1 << 16) + +/* Fields in TEGRA20_SPDIF_STROBE_CTRL */ + +/* + * Indicates the approximate number of detected SPDIFIN clocks within a + * bi-phase period. + */ +#define TEGRA20_SPDIF_STROBE_CTRL_PERIOD_SHIFT 16 +#define TEGRA20_SPDIF_STROBE_CTRL_PERIOD_MASK (0xff << TEGRA20_SPDIF_STROBE_CTRL_PERIOD_SHIFT) + +/* Data strobe mode: 0=Auto-locked 1=Manual locked */ +#define TEGRA20_SPDIF_STROBE_CTRL_STROBE (1 << 15) + +/* + * Manual data strobe time within the bi-phase clock period (in terms of + * the number of over-sampling clocks). + */ +#define TEGRA20_SPDIF_STROBE_CTRL_DATA_STROBES_SHIFT 8 +#define TEGRA20_SPDIF_STROBE_CTRL_DATA_STROBES_MASK (0x1f << TEGRA20_SPDIF_STROBE_CTRL_DATA_STROBES_SHIFT) + +/* + * Manual SPDIFIN bi-phase clock period (in terms of the number of + * over-sampling clocks). + */ +#define TEGRA20_SPDIF_STROBE_CTRL_CLOCK_PERIOD_SHIFT 0 +#define TEGRA20_SPDIF_STROBE_CTRL_CLOCK_PERIOD_MASK (0x3f << TEGRA20_SPDIF_STROBE_CTRL_CLOCK_PERIOD_SHIFT) + +/* Fields in SPDIF_DATA_FIFO_CSR */ + +/* Clear Receiver User FIFO (RX USR.FIFO) */ +#define TEGRA20_SPDIF_DATA_FIFO_CSR_RU_CLR (1 << 31) + +#define TEGRA20_SPDIF_FIFO_ATN_LVL_U_ONE_SLOT 0 +#define TEGRA20_SPDIF_FIFO_ATN_LVL_U_TWO_SLOTS 1 +#define TEGRA20_SPDIF_FIFO_ATN_LVL_U_THREE_SLOTS 2 +#define TEGRA20_SPDIF_FIFO_ATN_LVL_U_FOUR_SLOTS 3 + +/* RU FIFO attention level */ +#define TEGRA20_SPDIF_DATA_FIFO_CSR_RU_ATN_LVL_SHIFT 29 +#define TEGRA20_SPDIF_DATA_FIFO_CSR_RU_ATN_LVL_MASK \ + (0x3 << TEGRA20_SPDIF_DATA_FIFO_CSR_RU_ATN_LVL_SHIFT) +#define TEGRA20_SPDIF_DATA_FIFO_CSR_RU_ATN_LVL_RU1_WORD_FULL \ + (TEGRA20_SPDIF_FIFO_ATN_LVL_U_ONE_SLOT << TEGRA20_SPDIF_DATA_FIFO_CSR_RU_ATN_LVL_SHIFT) +#define TEGRA20_SPDIF_DATA_FIFO_CSR_RU_ATN_LVL_RU2_WORD_FULL \ + (TEGRA20_SPDIF_FIFO_ATN_LVL_U_TWO_SLOTS << TEGRA20_SPDIF_DATA_FIFO_CSR_RU_ATN_LVL_SHIFT) +#define TEGRA20_SPDIF_DATA_FIFO_CSR_RU_ATN_LVL_RU3_WORD_FULL \ + (TEGRA20_SPDIF_FIFO_ATN_LVL_U_THREE_SLOTS << TEGRA20_SPDIF_DATA_FIFO_CSR_RU_ATN_LVL_SHIFT) +#define TEGRA20_SPDIF_DATA_FIFO_CSR_RU_ATN_LVL_RU4_WORD_FULL \ + (TEGRA20_SPDIF_FIFO_ATN_LVL_U_FOUR_SLOTS << TEGRA20_SPDIF_DATA_FIFO_CSR_RU_ATN_LVL_SHIFT) + +/* Number of RX USR.FIFO levels with valid data. */ +#define TEGRA20_SPDIF_DATA_FIFO_CSR_RU_FULL_COUNT_SHIFT 24 +#define TEGRA20_SPDIF_DATA_FIFO_CSR_RU_FULL_COUNT_MASK (0x1f << TEGRA20_SPDIF_DATA_FIFO_CSR_RU_FULL_COUNT_SHIFT) + +/* Clear Transmitter User FIFO (TX USR.FIFO) */ +#define TEGRA20_SPDIF_DATA_FIFO_CSR_TU_CLR (1 << 23) + +/* TU FIFO attention level */ +#define TEGRA20_SPDIF_DATA_FIFO_CSR_TU_ATN_LVL_SHIFT 21 +#define TEGRA20_SPDIF_DATA_FIFO_CSR_TU_ATN_LVL_MASK \ + (0x3 << TEGRA20_SPDIF_DATA_FIFO_CSR_TU_ATN_LVL_SHIFT) +#define TEGRA20_SPDIF_DATA_FIFO_CSR_TU_ATN_LVL_TU1_WORD_FULL \ + (TEGRA20_SPDIF_FIFO_ATN_LVL_U_ONE_SLOT << TEGRA20_SPDIF_DATA_FIFO_CSR_TU_ATN_LVL_SHIFT) +#define TEGRA20_SPDIF_DATA_FIFO_CSR_TU_ATN_LVL_TU2_WORD_FULL \ + (TEGRA20_SPDIF_FIFO_ATN_LVL_U_TWO_SLOTS << TEGRA20_SPDIF_DATA_FIFO_CSR_TU_ATN_LVL_SHIFT) +#define TEGRA20_SPDIF_DATA_FIFO_CSR_TU_ATN_LVL_TU3_WORD_FULL \ + (TEGRA20_SPDIF_FIFO_ATN_LVL_U_THREE_SLOTS << TEGRA20_SPDIF_DATA_FIFO_CSR_TU_ATN_LVL_SHIFT) +#define TEGRA20_SPDIF_DATA_FIFO_CSR_TU_ATN_LVL_TU4_WORD_FULL \ + (TEGRA20_SPDIF_FIFO_ATN_LVL_U_FOUR_SLOTS << TEGRA20_SPDIF_DATA_FIFO_CSR_TU_ATN_LVL_SHIFT) + +/* Number of TX USR.FIFO levels that could be filled. */ +#define TEGRA20_SPDIF_DATA_FIFO_CSR_TU_EMPTY_COUNT_SHIFT 16 +#define TEGRA20_SPDIF_DATA_FIFO_CSR_TU_EMPTY_COUNT_MASK (0x1f << SPDIF_DATA_FIFO_CSR_TU_EMPTY_COUNT_SHIFT) + +/* Clear Receiver Data FIFO (RX DATA.FIFO) */ +#define TEGRA20_SPDIF_DATA_FIFO_CSR_RX_CLR (1 << 15) + +#define TEGRA20_SPDIF_FIFO_ATN_LVL_D_ONE_SLOT 0 +#define TEGRA20_SPDIF_FIFO_ATN_LVL_D_FOUR_SLOTS 1 +#define TEGRA20_SPDIF_FIFO_ATN_LVL_D_EIGHT_SLOTS 2 +#define TEGRA20_SPDIF_FIFO_ATN_LVL_D_TWELVE_SLOTS 3 + +/* RU FIFO attention level */ +#define TEGRA20_SPDIF_DATA_FIFO_CSR_RX_ATN_LVL_SHIFT 13 +#define TEGRA20_SPDIF_DATA_FIFO_CSR_RX_ATN_LVL_MASK \ + (0x3 << TEGRA20_SPDIF_DATA_FIFO_CSR_RX_ATN_LVL_SHIFT) +#define TEGRA20_SPDIF_DATA_FIFO_CSR_RX_ATN_LVL_RU1_WORD_FULL \ + (TEGRA20_SPDIF_FIFO_ATN_LVL_D_ONE_SLOT << TEGRA20_SPDIF_DATA_FIFO_CSR_RX_ATN_LVL_SHIFT) +#define TEGRA20_SPDIF_DATA_FIFO_CSR_RX_ATN_LVL_RU4_WORD_FULL \ + (TEGRA20_SPDIF_FIFO_ATN_LVL_D_FOUR_SLOTS << TEGRA20_SPDIF_DATA_FIFO_CSR_RX_ATN_LVL_SHIFT) +#define TEGRA20_SPDIF_DATA_FIFO_CSR_RX_ATN_LVL_RU8_WORD_FULL \ + (TEGRA20_SPDIF_FIFO_ATN_LVL_D_EIGHT_SLOTS << TEGRA20_SPDIF_DATA_FIFO_CSR_RX_ATN_LVL_SHIFT) +#define TEGRA20_SPDIF_DATA_FIFO_CSR_RX_ATN_LVL_RU12_WORD_FULL \ + (TEGRA20_SPDIF_FIFO_ATN_LVL_D_TWELVE_SLOTS << TEGRA20_SPDIF_DATA_FIFO_CSR_RX_ATN_LVL_SHIFT) + +/* Number of RX DATA.FIFO levels with valid data. */ +#define TEGRA20_SPDIF_DATA_FIFO_CSR_RX_FULL_COUNT_SHIFT 8 +#define TEGRA20_SPDIF_DATA_FIFO_CSR_RX_FULL_COUNT_MASK (0x1f << TEGRA20_SPDIF_DATA_FIFO_CSR_RX_FULL_COUNT_SHIFT) + +/* Clear Transmitter Data FIFO (TX DATA.FIFO) */ +#define TEGRA20_SPDIF_DATA_FIFO_CSR_TX_CLR (1 << 7) + +/* TU FIFO attention level */ +#define TEGRA20_SPDIF_DATA_FIFO_CSR_TX_ATN_LVL_SHIFT 5 +#define TEGRA20_SPDIF_DATA_FIFO_CSR_TX_ATN_LVL_MASK \ + (0x3 << TEGRA20_SPDIF_DATA_FIFO_CSR_TX_ATN_LVL_SHIFT) +#define TEGRA20_SPDIF_DATA_FIFO_CSR_TX_ATN_LVL_TU1_WORD_FULL \ + (TEGRA20_SPDIF_FIFO_ATN_LVL_D_ONE_SLOT << TEGRA20_SPDIF_DATA_FIFO_CSR_TX_ATN_LVL_SHIFT) +#define TEGRA20_SPDIF_DATA_FIFO_CSR_TX_ATN_LVL_TU4_WORD_FULL \ + (TEGRA20_SPDIF_FIFO_ATN_LVL_D_FOUR_SLOTS << TEGRA20_SPDIF_DATA_FIFO_CSR_TX_ATN_LVL_SHIFT) +#define TEGRA20_SPDIF_DATA_FIFO_CSR_TX_ATN_LVL_TU8_WORD_FULL \ + (TEGRA20_SPDIF_FIFO_ATN_LVL_D_EIGHT_SLOTS << TEGRA20_SPDIF_DATA_FIFO_CSR_TX_ATN_LVL_SHIFT) +#define TEGRA20_SPDIF_DATA_FIFO_CSR_TX_ATN_LVL_TU12_WORD_FULL \ + (TEGRA20_SPDIF_FIFO_ATN_LVL_D_TWELVE_SLOTS << TEGRA20_SPDIF_DATA_FIFO_CSR_TX_ATN_LVL_SHIFT) + +/* Number of TX DATA.FIFO levels that could be filled. */ +#define TEGRA20_SPDIF_DATA_FIFO_CSR_TX_EMPTY_COUNT_SHIFT 0 +#define TEGRA20_SPDIF_DATA_FIFO_CSR_TX_EMPTY_COUNT_MASK (0x1f << SPDIF_DATA_FIFO_CSR_TX_EMPTY_COUNT_SHIFT) + +/* Fields in TEGRA20_SPDIF_DATA_OUT */ + +/* + * This register has 5 different formats: + * 16-bit (BIT_MODE=00, PACK=0) + * 20-bit (BIT_MODE=01, PACK=0) + * 24-bit (BIT_MODE=10, PACK=0) + * raw (BIT_MODE=11, PACK=0) + * 16-bit packed (BIT_MODE=00, PACK=1) + */ + +#define TEGRA20_SPDIF_DATA_OUT_DATA_16_SHIFT 0 +#define TEGRA20_SPDIF_DATA_OUT_DATA_16_MASK (0xffff << TEGRA20_SPDIF_DATA_OUT_DATA_16_SHIFT) + +#define TEGRA20_SPDIF_DATA_OUT_DATA_20_SHIFT 0 +#define TEGRA20_SPDIF_DATA_OUT_DATA_20_MASK (0xfffff << TEGRA20_SPDIF_DATA_OUT_DATA_20_SHIFT) + +#define TEGRA20_SPDIF_DATA_OUT_DATA_24_SHIFT 0 +#define TEGRA20_SPDIF_DATA_OUT_DATA_24_MASK (0xffffff << TEGRA20_SPDIF_DATA_OUT_DATA_24_SHIFT) + +#define TEGRA20_SPDIF_DATA_OUT_DATA_RAW_P (1 << 31) +#define TEGRA20_SPDIF_DATA_OUT_DATA_RAW_C (1 << 30) +#define TEGRA20_SPDIF_DATA_OUT_DATA_RAW_U (1 << 29) +#define TEGRA20_SPDIF_DATA_OUT_DATA_RAW_V (1 << 28) + +#define TEGRA20_SPDIF_DATA_OUT_DATA_RAW_DATA_SHIFT 8 +#define TEGRA20_SPDIF_DATA_OUT_DATA_RAW_DATA_MASK (0xfffff << TEGRA20_SPDIF_DATA_OUT_DATA_RAW_DATA_SHIFT) + +#define TEGRA20_SPDIF_DATA_OUT_DATA_RAW_AUX_SHIFT 4 +#define TEGRA20_SPDIF_DATA_OUT_DATA_RAW_AUX_MASK (0xf << TEGRA20_SPDIF_DATA_OUT_DATA_RAW_AUX_SHIFT) + +#define TEGRA20_SPDIF_DATA_OUT_DATA_RAW_PREAMBLE_SHIFT 0 +#define TEGRA20_SPDIF_DATA_OUT_DATA_RAW_PREAMBLE_MASK (0xf << TEGRA20_SPDIF_DATA_OUT_DATA_RAW_PREAMBLE_SHIFT) + +#define TEGRA20_SPDIF_DATA_OUT_DATA_16_PACKED_RIGHT_SHIFT 16 +#define TEGRA20_SPDIF_DATA_OUT_DATA_16_PACKED_RIGHT_MASK (0xffff << TEGRA20_SPDIF_DATA_OUT_DATA_16_PACKED_RIGHT_SHIFT) + +#define TEGRA20_SPDIF_DATA_OUT_DATA_16_PACKED_LEFT_SHIFT 0 +#define TEGRA20_SPDIF_DATA_OUT_DATA_16_PACKED_LEFT_MASK (0xffff << TEGRA20_SPDIF_DATA_OUT_DATA_16_PACKED_LEFT_SHIFT) + +/* Fields in TEGRA20_SPDIF_DATA_IN */ + +/* + * This register has 5 different formats: + * 16-bit (BIT_MODE=00, PACK=0) + * 20-bit (BIT_MODE=01, PACK=0) + * 24-bit (BIT_MODE=10, PACK=0) + * raw (BIT_MODE=11, PACK=0) + * 16-bit packed (BIT_MODE=00, PACK=1) + * + * Bits 31:24 are common to all modes except 16-bit packed + */ + +#define TEGRA20_SPDIF_DATA_IN_DATA_P (1 << 31) +#define TEGRA20_SPDIF_DATA_IN_DATA_C (1 << 30) +#define TEGRA20_SPDIF_DATA_IN_DATA_U (1 << 29) +#define TEGRA20_SPDIF_DATA_IN_DATA_V (1 << 28) + +#define TEGRA20_SPDIF_DATA_IN_DATA_PREAMBLE_SHIFT 24 +#define TEGRA20_SPDIF_DATA_IN_DATA_PREAMBLE_MASK (0xf << TEGRA20_SPDIF_DATA_IN_DATA_PREAMBLE_SHIFT) + +#define TEGRA20_SPDIF_DATA_IN_DATA_16_SHIFT 0 +#define TEGRA20_SPDIF_DATA_IN_DATA_16_MASK (0xffff << TEGRA20_SPDIF_DATA_IN_DATA_16_SHIFT) + +#define TEGRA20_SPDIF_DATA_IN_DATA_20_SHIFT 0 +#define TEGRA20_SPDIF_DATA_IN_DATA_20_MASK (0xfffff << TEGRA20_SPDIF_DATA_IN_DATA_20_SHIFT) + +#define TEGRA20_SPDIF_DATA_IN_DATA_24_SHIFT 0 +#define TEGRA20_SPDIF_DATA_IN_DATA_24_MASK (0xffffff << TEGRA20_SPDIF_DATA_IN_DATA_24_SHIFT) + +#define TEGRA20_SPDIF_DATA_IN_DATA_RAW_DATA_SHIFT 8 +#define TEGRA20_SPDIF_DATA_IN_DATA_RAW_DATA_MASK (0xfffff << TEGRA20_SPDIF_DATA_IN_DATA_RAW_DATA_SHIFT) + +#define TEGRA20_SPDIF_DATA_IN_DATA_RAW_AUX_SHIFT 4 +#define TEGRA20_SPDIF_DATA_IN_DATA_RAW_AUX_MASK (0xf << TEGRA20_SPDIF_DATA_IN_DATA_RAW_AUX_SHIFT) + +#define TEGRA20_SPDIF_DATA_IN_DATA_RAW_PREAMBLE_SHIFT 0 +#define TEGRA20_SPDIF_DATA_IN_DATA_RAW_PREAMBLE_MASK (0xf << TEGRA20_SPDIF_DATA_IN_DATA_RAW_PREAMBLE_SHIFT) + +#define TEGRA20_SPDIF_DATA_IN_DATA_16_PACKED_RIGHT_SHIFT 16 +#define TEGRA20_SPDIF_DATA_IN_DATA_16_PACKED_RIGHT_MASK (0xffff << TEGRA20_SPDIF_DATA_IN_DATA_16_PACKED_RIGHT_SHIFT) + +#define TEGRA20_SPDIF_DATA_IN_DATA_16_PACKED_LEFT_SHIFT 0 +#define TEGRA20_SPDIF_DATA_IN_DATA_16_PACKED_LEFT_MASK (0xffff << TEGRA20_SPDIF_DATA_IN_DATA_16_PACKED_LEFT_SHIFT) + +/* Fields in TEGRA20_SPDIF_CH_STA_RX_A */ +/* Fields in TEGRA20_SPDIF_CH_STA_RX_B */ +/* Fields in TEGRA20_SPDIF_CH_STA_RX_C */ +/* Fields in TEGRA20_SPDIF_CH_STA_RX_D */ +/* Fields in TEGRA20_SPDIF_CH_STA_RX_E */ +/* Fields in TEGRA20_SPDIF_CH_STA_RX_F */ + +/* + * The 6-word receive channel data page buffer holds a block (192 frames) of + * channel status information. The order of receive is from LSB to MSB + * bit, and from CH_STA_RX_A to CH_STA_RX_F then back to CH_STA_RX_A. + */ + +/* Fields in TEGRA20_SPDIF_CH_STA_TX_A */ +/* Fields in TEGRA20_SPDIF_CH_STA_TX_B */ +/* Fields in TEGRA20_SPDIF_CH_STA_TX_C */ +/* Fields in TEGRA20_SPDIF_CH_STA_TX_D */ +/* Fields in TEGRA20_SPDIF_CH_STA_TX_E */ +/* Fields in TEGRA20_SPDIF_CH_STA_TX_F */ + +/* + * The 6-word transmit channel data page buffer holds a block (192 frames) of + * channel status information. The order of transmission is from LSB to MSB + * bit, and from CH_STA_TX_A to CH_STA_TX_F then back to CH_STA_TX_A. + */ + +/* Fields in TEGRA20_SPDIF_USR_STA_RX_A */ + +/* + * This 4-word deep FIFO receives user FIFO field information. The order of + * receive is from LSB to MSB bit. + */ + +/* Fields in TEGRA20_SPDIF_USR_DAT_TX_A */ + +/* + * This 4-word deep FIFO transmits user FIFO field information. The order of + * transmission is from LSB to MSB bit. + */ + +struct tegra20_spdif { + struct clk *clk_spdif_out; + struct tegra_pcm_dma_params capture_dma_data; + struct tegra_pcm_dma_params playback_dma_data; + struct regmap *regmap; + u32 reg_ctrl; +}; + +#endif diff --git a/sound/soc/tegra/tegra30_ahub.c b/sound/soc/tegra/tegra30_ahub.c new file mode 100644 index 00000000000..57cd419f743 --- /dev/null +++ b/sound/soc/tegra/tegra30_ahub.c @@ -0,0 +1,631 @@ +/* + * tegra30_ahub.c - Tegra30 AHUB driver + * + * Copyright (c) 2011,2012, NVIDIA CORPORATION. All rights reserved. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/clk.h> +#include <linux/device.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <linux/slab.h> +#include <mach/clk.h> +#include <mach/dma.h> +#include <sound/soc.h> +#include "tegra30_ahub.h" + +#define DRV_NAME "tegra30-ahub" + +static struct tegra30_ahub *ahub; + +static inline void tegra30_apbif_write(u32 reg, u32 val) +{ + regmap_write(ahub->regmap_apbif, reg, val); +} + +static inline u32 tegra30_apbif_read(u32 reg) +{ + u32 val; + regmap_read(ahub->regmap_apbif, reg, &val); + return val; +} + +static inline void tegra30_audio_write(u32 reg, u32 val) +{ + regmap_write(ahub->regmap_ahub, reg, val); +} + +static int tegra30_ahub_runtime_suspend(struct device *dev) +{ + regcache_cache_only(ahub->regmap_apbif, true); + regcache_cache_only(ahub->regmap_ahub, true); + + clk_disable(ahub->clk_apbif); + clk_disable(ahub->clk_d_audio); + + return 0; +} + +/* + * clk_apbif isn't required for an I2S<->I2S configuration where no PCM data + * is read from or sent to memory. However, that's not something the rest of + * the driver supports right now, so we'll just treat the two clocks as one + * for now. + * + * These functions should not be a plain ref-count. Instead, each active stream + * contributes some requirement to the minimum clock rate, so starting or + * stopping streams should dynamically adjust the clock as required. However, + * this is not yet implemented. + */ +static int tegra30_ahub_runtime_resume(struct device *dev) +{ + int ret; + + ret = clk_enable(ahub->clk_d_audio); + if (ret) { + dev_err(dev, "clk_enable d_audio failed: %d\n", ret); + return ret; + } + ret = clk_enable(ahub->clk_apbif); + if (ret) { + dev_err(dev, "clk_enable apbif failed: %d\n", ret); + clk_disable(ahub->clk_d_audio); + return ret; + } + + regcache_cache_only(ahub->regmap_apbif, false); + regcache_cache_only(ahub->regmap_ahub, false); + + return 0; +} + +int tegra30_ahub_allocate_rx_fifo(enum tegra30_ahub_rxcif *rxcif, + unsigned long *fiforeg, + unsigned long *reqsel) +{ + int channel; + u32 reg, val; + + channel = find_first_zero_bit(ahub->rx_usage, + TEGRA30_AHUB_CHANNEL_CTRL_COUNT); + if (channel >= TEGRA30_AHUB_CHANNEL_CTRL_COUNT) + return -EBUSY; + + __set_bit(channel, ahub->rx_usage); + + *rxcif = TEGRA30_AHUB_RXCIF_APBIF_RX0 + channel; + *fiforeg = ahub->apbif_addr + TEGRA30_AHUB_CHANNEL_RXFIFO + + (channel * TEGRA30_AHUB_CHANNEL_RXFIFO_STRIDE); + *reqsel = ahub->dma_sel + channel; + + reg = TEGRA30_AHUB_CHANNEL_CTRL + + (channel * TEGRA30_AHUB_CHANNEL_CTRL_STRIDE); + val = tegra30_apbif_read(reg); + val &= ~(TEGRA30_AHUB_CHANNEL_CTRL_RX_THRESHOLD_MASK | + TEGRA30_AHUB_CHANNEL_CTRL_RX_PACK_MASK); + val |= (7 << TEGRA30_AHUB_CHANNEL_CTRL_RX_THRESHOLD_SHIFT) | + TEGRA30_AHUB_CHANNEL_CTRL_RX_PACK_EN | + TEGRA30_AHUB_CHANNEL_CTRL_RX_PACK_16; + tegra30_apbif_write(reg, val); + + reg = TEGRA30_AHUB_CIF_RX_CTRL + + (channel * TEGRA30_AHUB_CIF_RX_CTRL_STRIDE); + val = (0 << TEGRA30_AUDIOCIF_CTRL_FIFO_THRESHOLD_SHIFT) | + (1 << TEGRA30_AUDIOCIF_CTRL_AUDIO_CHANNELS_SHIFT) | + (1 << TEGRA30_AUDIOCIF_CTRL_CLIENT_CHANNELS_SHIFT) | + TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_16 | + TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_16 | + TEGRA30_AUDIOCIF_CTRL_DIRECTION_RX; + tegra30_apbif_write(reg, val); + + return 0; +} +EXPORT_SYMBOL_GPL(tegra30_ahub_allocate_rx_fifo); + +int tegra30_ahub_enable_rx_fifo(enum tegra30_ahub_rxcif rxcif) +{ + int channel = rxcif - TEGRA30_AHUB_RXCIF_APBIF_RX0; + int reg, val; + + reg = TEGRA30_AHUB_CHANNEL_CTRL + + (channel * TEGRA30_AHUB_CHANNEL_CTRL_STRIDE); + val = tegra30_apbif_read(reg); + val |= TEGRA30_AHUB_CHANNEL_CTRL_RX_EN; + tegra30_apbif_write(reg, val); + + return 0; +} +EXPORT_SYMBOL_GPL(tegra30_ahub_enable_rx_fifo); + +int tegra30_ahub_disable_rx_fifo(enum tegra30_ahub_rxcif rxcif) +{ + int channel = rxcif - TEGRA30_AHUB_RXCIF_APBIF_RX0; + int reg, val; + + reg = TEGRA30_AHUB_CHANNEL_CTRL + + (channel * TEGRA30_AHUB_CHANNEL_CTRL_STRIDE); + val = tegra30_apbif_read(reg); + val &= ~TEGRA30_AHUB_CHANNEL_CTRL_RX_EN; + tegra30_apbif_write(reg, val); + + return 0; +} +EXPORT_SYMBOL_GPL(tegra30_ahub_disable_rx_fifo); + +int tegra30_ahub_free_rx_fifo(enum tegra30_ahub_rxcif rxcif) +{ + int channel = rxcif - TEGRA30_AHUB_RXCIF_APBIF_RX0; + + __clear_bit(channel, ahub->rx_usage); + + return 0; +} +EXPORT_SYMBOL_GPL(tegra30_ahub_free_rx_fifo); + +int tegra30_ahub_allocate_tx_fifo(enum tegra30_ahub_txcif *txcif, + unsigned long *fiforeg, + unsigned long *reqsel) +{ + int channel; + u32 reg, val; + + channel = find_first_zero_bit(ahub->tx_usage, + TEGRA30_AHUB_CHANNEL_CTRL_COUNT); + if (channel >= TEGRA30_AHUB_CHANNEL_CTRL_COUNT) + return -EBUSY; + + __set_bit(channel, ahub->tx_usage); + + *txcif = TEGRA30_AHUB_TXCIF_APBIF_TX0 + channel; + *fiforeg = ahub->apbif_addr + TEGRA30_AHUB_CHANNEL_TXFIFO + + (channel * TEGRA30_AHUB_CHANNEL_TXFIFO_STRIDE); + *reqsel = ahub->dma_sel + channel; + + reg = TEGRA30_AHUB_CHANNEL_CTRL + + (channel * TEGRA30_AHUB_CHANNEL_CTRL_STRIDE); + val = tegra30_apbif_read(reg); + val &= ~(TEGRA30_AHUB_CHANNEL_CTRL_TX_THRESHOLD_MASK | + TEGRA30_AHUB_CHANNEL_CTRL_TX_PACK_MASK); + val |= (7 << TEGRA30_AHUB_CHANNEL_CTRL_TX_THRESHOLD_SHIFT) | + TEGRA30_AHUB_CHANNEL_CTRL_TX_PACK_EN | + TEGRA30_AHUB_CHANNEL_CTRL_TX_PACK_16; + tegra30_apbif_write(reg, val); + + reg = TEGRA30_AHUB_CIF_TX_CTRL + + (channel * TEGRA30_AHUB_CIF_TX_CTRL_STRIDE); + val = (0 << TEGRA30_AUDIOCIF_CTRL_FIFO_THRESHOLD_SHIFT) | + (1 << TEGRA30_AUDIOCIF_CTRL_AUDIO_CHANNELS_SHIFT) | + (1 << TEGRA30_AUDIOCIF_CTRL_CLIENT_CHANNELS_SHIFT) | + TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_16 | + TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_16 | + TEGRA30_AUDIOCIF_CTRL_DIRECTION_TX; + tegra30_apbif_write(reg, val); + + return 0; +} +EXPORT_SYMBOL_GPL(tegra30_ahub_allocate_tx_fifo); + +int tegra30_ahub_enable_tx_fifo(enum tegra30_ahub_txcif txcif) +{ + int channel = txcif - TEGRA30_AHUB_TXCIF_APBIF_TX0; + int reg, val; + + reg = TEGRA30_AHUB_CHANNEL_CTRL + + (channel * TEGRA30_AHUB_CHANNEL_CTRL_STRIDE); + val = tegra30_apbif_read(reg); + val |= TEGRA30_AHUB_CHANNEL_CTRL_TX_EN; + tegra30_apbif_write(reg, val); + + return 0; +} +EXPORT_SYMBOL_GPL(tegra30_ahub_enable_tx_fifo); + +int tegra30_ahub_disable_tx_fifo(enum tegra30_ahub_txcif txcif) +{ + int channel = txcif - TEGRA30_AHUB_TXCIF_APBIF_TX0; + int reg, val; + + reg = TEGRA30_AHUB_CHANNEL_CTRL + + (channel * TEGRA30_AHUB_CHANNEL_CTRL_STRIDE); + val = tegra30_apbif_read(reg); + val &= ~TEGRA30_AHUB_CHANNEL_CTRL_TX_EN; + tegra30_apbif_write(reg, val); + + return 0; +} +EXPORT_SYMBOL_GPL(tegra30_ahub_disable_tx_fifo); + +int tegra30_ahub_free_tx_fifo(enum tegra30_ahub_txcif txcif) +{ + int channel = txcif - TEGRA30_AHUB_TXCIF_APBIF_TX0; + + __clear_bit(channel, ahub->tx_usage); + + return 0; +} +EXPORT_SYMBOL_GPL(tegra30_ahub_free_tx_fifo); + +int tegra30_ahub_set_rx_cif_source(enum tegra30_ahub_rxcif rxcif, + enum tegra30_ahub_txcif txcif) +{ + int channel = rxcif - TEGRA30_AHUB_RXCIF_APBIF_RX0; + int reg; + + reg = TEGRA30_AHUB_AUDIO_RX + + (channel * TEGRA30_AHUB_AUDIO_RX_STRIDE); + tegra30_audio_write(reg, 1 << txcif); + + return 0; +} +EXPORT_SYMBOL_GPL(tegra30_ahub_set_rx_cif_source); + +int tegra30_ahub_unset_rx_cif_source(enum tegra30_ahub_rxcif rxcif) +{ + int channel = rxcif - TEGRA30_AHUB_RXCIF_APBIF_RX0; + int reg; + + reg = TEGRA30_AHUB_AUDIO_RX + + (channel * TEGRA30_AHUB_AUDIO_RX_STRIDE); + tegra30_audio_write(reg, 0); + + return 0; +} +EXPORT_SYMBOL_GPL(tegra30_ahub_unset_rx_cif_source); + +static const char * const configlink_clocks[] __devinitconst = { + "i2s0", + "i2s1", + "i2s2", + "i2s3", + "i2s4", + "dam0", + "dam1", + "dam2", + "spdif_in", +}; + +struct of_dev_auxdata ahub_auxdata[] __devinitdata = { + OF_DEV_AUXDATA("nvidia,tegra30-i2s", 0x70080300, "tegra30-i2s.0", NULL), + OF_DEV_AUXDATA("nvidia,tegra30-i2s", 0x70080400, "tegra30-i2s.1", NULL), + OF_DEV_AUXDATA("nvidia,tegra30-i2s", 0x70080500, "tegra30-i2s.2", NULL), + OF_DEV_AUXDATA("nvidia,tegra30-i2s", 0x70080600, "tegra30-i2s.3", NULL), + OF_DEV_AUXDATA("nvidia,tegra30-i2s", 0x70080700, "tegra30-i2s.4", NULL), + {} +}; + +#define LAST_REG(name) \ + (TEGRA30_AHUB_##name + \ + (TEGRA30_AHUB_##name##_STRIDE * TEGRA30_AHUB_##name##_COUNT) - 4) + +#define REG_IN_ARRAY(reg, name) \ + ((reg >= TEGRA30_AHUB_##name) && \ + (reg <= LAST_REG(name) && \ + (!((reg - TEGRA30_AHUB_##name) % TEGRA30_AHUB_##name##_STRIDE)))) + +static bool tegra30_ahub_apbif_wr_rd_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TEGRA30_AHUB_CONFIG_LINK_CTRL: + case TEGRA30_AHUB_MISC_CTRL: + case TEGRA30_AHUB_APBDMA_LIVE_STATUS: + case TEGRA30_AHUB_I2S_LIVE_STATUS: + case TEGRA30_AHUB_SPDIF_LIVE_STATUS: + case TEGRA30_AHUB_I2S_INT_MASK: + case TEGRA30_AHUB_DAM_INT_MASK: + case TEGRA30_AHUB_SPDIF_INT_MASK: + case TEGRA30_AHUB_APBIF_INT_MASK: + case TEGRA30_AHUB_I2S_INT_STATUS: + case TEGRA30_AHUB_DAM_INT_STATUS: + case TEGRA30_AHUB_SPDIF_INT_STATUS: + case TEGRA30_AHUB_APBIF_INT_STATUS: + case TEGRA30_AHUB_I2S_INT_SOURCE: + case TEGRA30_AHUB_DAM_INT_SOURCE: + case TEGRA30_AHUB_SPDIF_INT_SOURCE: + case TEGRA30_AHUB_APBIF_INT_SOURCE: + case TEGRA30_AHUB_I2S_INT_SET: + case TEGRA30_AHUB_DAM_INT_SET: + case TEGRA30_AHUB_SPDIF_INT_SET: + case TEGRA30_AHUB_APBIF_INT_SET: + return true; + default: + break; + }; + + if (REG_IN_ARRAY(reg, CHANNEL_CTRL) || + REG_IN_ARRAY(reg, CHANNEL_CLEAR) || + REG_IN_ARRAY(reg, CHANNEL_STATUS) || + REG_IN_ARRAY(reg, CHANNEL_TXFIFO) || + REG_IN_ARRAY(reg, CHANNEL_RXFIFO) || + REG_IN_ARRAY(reg, CIF_TX_CTRL) || + REG_IN_ARRAY(reg, CIF_RX_CTRL) || + REG_IN_ARRAY(reg, DAM_LIVE_STATUS)) + return true; + + return false; +} + +static bool tegra30_ahub_apbif_volatile_reg(struct device *dev, + unsigned int reg) +{ + switch (reg) { + case TEGRA30_AHUB_CONFIG_LINK_CTRL: + case TEGRA30_AHUB_MISC_CTRL: + case TEGRA30_AHUB_APBDMA_LIVE_STATUS: + case TEGRA30_AHUB_I2S_LIVE_STATUS: + case TEGRA30_AHUB_SPDIF_LIVE_STATUS: + case TEGRA30_AHUB_I2S_INT_STATUS: + case TEGRA30_AHUB_DAM_INT_STATUS: + case TEGRA30_AHUB_SPDIF_INT_STATUS: + case TEGRA30_AHUB_APBIF_INT_STATUS: + case TEGRA30_AHUB_I2S_INT_SET: + case TEGRA30_AHUB_DAM_INT_SET: + case TEGRA30_AHUB_SPDIF_INT_SET: + case TEGRA30_AHUB_APBIF_INT_SET: + return true; + default: + break; + }; + + if (REG_IN_ARRAY(reg, CHANNEL_CLEAR) || + REG_IN_ARRAY(reg, CHANNEL_STATUS) || + REG_IN_ARRAY(reg, CHANNEL_TXFIFO) || + REG_IN_ARRAY(reg, CHANNEL_RXFIFO) || + REG_IN_ARRAY(reg, DAM_LIVE_STATUS)) + return true; + + return false; +} + +static bool tegra30_ahub_apbif_precious_reg(struct device *dev, + unsigned int reg) +{ + if (REG_IN_ARRAY(reg, CHANNEL_TXFIFO) || + REG_IN_ARRAY(reg, CHANNEL_RXFIFO)) + return true; + + return false; +} + +static const struct regmap_config tegra30_ahub_apbif_regmap_config = { + .name = "apbif", + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = TEGRA30_AHUB_APBIF_INT_SET, + .writeable_reg = tegra30_ahub_apbif_wr_rd_reg, + .readable_reg = tegra30_ahub_apbif_wr_rd_reg, + .volatile_reg = tegra30_ahub_apbif_volatile_reg, + .precious_reg = tegra30_ahub_apbif_precious_reg, + .cache_type = REGCACHE_RBTREE, +}; + +static bool tegra30_ahub_ahub_wr_rd_reg(struct device *dev, unsigned int reg) +{ + if (REG_IN_ARRAY(reg, AUDIO_RX)) + return true; + + return false; +} + +static const struct regmap_config tegra30_ahub_ahub_regmap_config = { + .name = "ahub", + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = LAST_REG(AUDIO_RX), + .writeable_reg = tegra30_ahub_ahub_wr_rd_reg, + .readable_reg = tegra30_ahub_ahub_wr_rd_reg, + .cache_type = REGCACHE_RBTREE, +}; + +static int __devinit tegra30_ahub_probe(struct platform_device *pdev) +{ + struct clk *clk; + int i; + struct resource *res0, *res1, *region; + u32 of_dma[2]; + void __iomem *regs_apbif, *regs_ahub; + int ret = 0; + + if (ahub) + return -ENODEV; + + /* + * The AHUB hosts a register bus: the "configlink". For this to + * operate correctly, all devices on this bus must be out of reset. + * Ensure that here. + */ + for (i = 0; i < ARRAY_SIZE(configlink_clocks); i++) { + clk = clk_get_sys(NULL, configlink_clocks[i]); + if (IS_ERR(clk)) { + dev_err(&pdev->dev, "Can't get clock %s\n", + configlink_clocks[i]); + ret = PTR_ERR(clk); + goto err; + } + tegra_periph_reset_deassert(clk); + clk_put(clk); + } + + ahub = devm_kzalloc(&pdev->dev, sizeof(struct tegra30_ahub), + GFP_KERNEL); + if (!ahub) { + dev_err(&pdev->dev, "Can't allocate tegra30_ahub\n"); + ret = -ENOMEM; + goto err; + } + dev_set_drvdata(&pdev->dev, ahub); + + ahub->dev = &pdev->dev; + + ahub->clk_d_audio = clk_get(&pdev->dev, "d_audio"); + if (IS_ERR(ahub->clk_d_audio)) { + dev_err(&pdev->dev, "Can't retrieve ahub d_audio clock\n"); + ret = PTR_ERR(ahub->clk_d_audio); + goto err; + } + + ahub->clk_apbif = clk_get(&pdev->dev, "apbif"); + if (IS_ERR(ahub->clk_apbif)) { + dev_err(&pdev->dev, "Can't retrieve ahub apbif clock\n"); + ret = PTR_ERR(ahub->clk_apbif); + goto err_clk_put_d_audio; + } + + if (of_property_read_u32_array(pdev->dev.of_node, + "nvidia,dma-request-selector", + of_dma, 2) < 0) { + dev_err(&pdev->dev, + "Missing property nvidia,dma-request-selector\n"); + ret = -ENODEV; + goto err_clk_put_d_audio; + } + ahub->dma_sel = of_dma[1]; + + res0 = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res0) { + dev_err(&pdev->dev, "No apbif memory resource\n"); + ret = -ENODEV; + goto err_clk_put_apbif; + } + + region = devm_request_mem_region(&pdev->dev, res0->start, + resource_size(res0), DRV_NAME); + if (!region) { + dev_err(&pdev->dev, "request region apbif failed\n"); + ret = -EBUSY; + goto err_clk_put_apbif; + } + ahub->apbif_addr = res0->start; + + regs_apbif = devm_ioremap(&pdev->dev, res0->start, + resource_size(res0)); + if (!regs_apbif) { + dev_err(&pdev->dev, "ioremap apbif failed\n"); + ret = -ENOMEM; + goto err_clk_put_apbif; + } + + ahub->regmap_apbif = devm_regmap_init_mmio(&pdev->dev, regs_apbif, + &tegra30_ahub_apbif_regmap_config); + if (IS_ERR(ahub->regmap_apbif)) { + dev_err(&pdev->dev, "apbif regmap init failed\n"); + ret = PTR_ERR(ahub->regmap_apbif); + goto err_clk_put_apbif; + } + regcache_cache_only(ahub->regmap_apbif, true); + + res1 = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (!res1) { + dev_err(&pdev->dev, "No ahub memory resource\n"); + ret = -ENODEV; + goto err_clk_put_apbif; + } + + region = devm_request_mem_region(&pdev->dev, res1->start, + resource_size(res1), DRV_NAME); + if (!region) { + dev_err(&pdev->dev, "request region ahub failed\n"); + ret = -EBUSY; + goto err_clk_put_apbif; + } + + regs_ahub = devm_ioremap(&pdev->dev, res1->start, + resource_size(res1)); + if (!regs_ahub) { + dev_err(&pdev->dev, "ioremap ahub failed\n"); + ret = -ENOMEM; + goto err_clk_put_apbif; + } + + ahub->regmap_ahub = devm_regmap_init_mmio(&pdev->dev, regs_ahub, + &tegra30_ahub_ahub_regmap_config); + if (IS_ERR(ahub->regmap_ahub)) { + dev_err(&pdev->dev, "ahub regmap init failed\n"); + ret = PTR_ERR(ahub->regmap_ahub); + goto err_clk_put_apbif; + } + regcache_cache_only(ahub->regmap_ahub, true); + + pm_runtime_enable(&pdev->dev); + if (!pm_runtime_enabled(&pdev->dev)) { + ret = tegra30_ahub_runtime_resume(&pdev->dev); + if (ret) + goto err_pm_disable; + } + + of_platform_populate(pdev->dev.of_node, NULL, ahub_auxdata, + &pdev->dev); + + return 0; + +err_pm_disable: + pm_runtime_disable(&pdev->dev); +err_clk_put_apbif: + clk_put(ahub->clk_apbif); +err_clk_put_d_audio: + clk_put(ahub->clk_d_audio); + ahub = 0; +err: + return ret; +} + +static int __devexit tegra30_ahub_remove(struct platform_device *pdev) +{ + if (!ahub) + return -ENODEV; + + pm_runtime_disable(&pdev->dev); + if (!pm_runtime_status_suspended(&pdev->dev)) + tegra30_ahub_runtime_suspend(&pdev->dev); + + clk_put(ahub->clk_apbif); + clk_put(ahub->clk_d_audio); + + ahub = 0; + + return 0; +} + +static const struct of_device_id tegra30_ahub_of_match[] __devinitconst = { + { .compatible = "nvidia,tegra30-ahub", }, + {}, +}; + +static const struct dev_pm_ops tegra30_ahub_pm_ops __devinitconst = { + SET_RUNTIME_PM_OPS(tegra30_ahub_runtime_suspend, + tegra30_ahub_runtime_resume, NULL) +}; + +static struct platform_driver tegra30_ahub_driver = { + .probe = tegra30_ahub_probe, + .remove = __devexit_p(tegra30_ahub_remove), + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + .of_match_table = tegra30_ahub_of_match, + .pm = &tegra30_ahub_pm_ops, + }, +}; +module_platform_driver(tegra30_ahub_driver); + +MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>"); +MODULE_DESCRIPTION("Tegra30 AHUB driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRV_NAME); diff --git a/sound/soc/tegra/tegra30_ahub.h b/sound/soc/tegra/tegra30_ahub.h new file mode 100644 index 00000000000..e690e2eecc9 --- /dev/null +++ b/sound/soc/tegra/tegra30_ahub.h @@ -0,0 +1,483 @@ +/* + * tegra30_ahub.h - Definitions for Tegra30 AHUB driver + * + * Copyright (c) 2011,2012, NVIDIA CORPORATION. All rights reserved. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef __TEGRA30_AHUB_H__ +#define __TEGRA30_AHUB_H__ + +/* Fields in *_CIF_RX/TX_CTRL; used by AHUB FIFOs, and all other audio modules */ + +#define TEGRA30_AUDIOCIF_CTRL_FIFO_THRESHOLD_SHIFT 28 +#define TEGRA30_AUDIOCIF_CTRL_FIFO_THRESHOLD_MASK_US 0xf +#define TEGRA30_AUDIOCIF_CTRL_FIFO_THRESHOLD_MASK (TEGRA30_AUDIOCIF_CTRL_FIFO_THRESHOLD_MASK_US << TEGRA30_AUDIOCIF_CTRL_FIFO_THRESHOLD_SHIFT) + +/* Channel count minus 1 */ +#define TEGRA30_AUDIOCIF_CTRL_AUDIO_CHANNELS_SHIFT 24 +#define TEGRA30_AUDIOCIF_CTRL_AUDIO_CHANNELS_MASK_US 7 +#define TEGRA30_AUDIOCIF_CTRL_AUDIO_CHANNELS_MASK (TEGRA30_AUDIOCIF_CTRL_AUDIO_CHANNELS_MASK_US << TEGRA30_AUDIOCIF_CTRL_AUDIO_CHANNELS_SHIFT) + +/* Channel count minus 1 */ +#define TEGRA30_AUDIOCIF_CTRL_CLIENT_CHANNELS_SHIFT 16 +#define TEGRA30_AUDIOCIF_CTRL_CLIENT_CHANNELS_MASK_US 7 +#define TEGRA30_AUDIOCIF_CTRL_CLIENT_CHANNELS_MASK (TEGRA30_AUDIOCIF_CTRL_CLIENT_CHANNELS_MASK_US << TEGRA30_AUDIOCIF_CTRL_CLIENT_CHANNELS_SHIFT) + +#define TEGRA30_AUDIOCIF_BITS_4 0 +#define TEGRA30_AUDIOCIF_BITS_8 1 +#define TEGRA30_AUDIOCIF_BITS_12 2 +#define TEGRA30_AUDIOCIF_BITS_16 3 +#define TEGRA30_AUDIOCIF_BITS_20 4 +#define TEGRA30_AUDIOCIF_BITS_24 5 +#define TEGRA30_AUDIOCIF_BITS_28 6 +#define TEGRA30_AUDIOCIF_BITS_32 7 + +#define TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_SHIFT 12 +#define TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_MASK (7 << TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_4 (TEGRA30_AUDIOCIF_BITS_4 << TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_8 (TEGRA30_AUDIOCIF_BITS_8 << TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_12 (TEGRA30_AUDIOCIF_BITS_12 << TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_16 (TEGRA30_AUDIOCIF_BITS_16 << TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_20 (TEGRA30_AUDIOCIF_BITS_20 << TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_24 (TEGRA30_AUDIOCIF_BITS_24 << TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_28 (TEGRA30_AUDIOCIF_BITS_28 << TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_32 (TEGRA30_AUDIOCIF_BITS_32 << TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_SHIFT) + +#define TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_SHIFT 8 +#define TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_MASK (7 << TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_4 (TEGRA30_AUDIOCIF_BITS_4 << TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_8 (TEGRA30_AUDIOCIF_BITS_8 << TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_12 (TEGRA30_AUDIOCIF_BITS_12 << TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_16 (TEGRA30_AUDIOCIF_BITS_16 << TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_20 (TEGRA30_AUDIOCIF_BITS_20 << TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_24 (TEGRA30_AUDIOCIF_BITS_24 << TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_28 (TEGRA30_AUDIOCIF_BITS_28 << TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_32 (TEGRA30_AUDIOCIF_BITS_32 << TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_SHIFT) + +#define TEGRA30_AUDIOCIF_EXPAND_ZERO 0 +#define TEGRA30_AUDIOCIF_EXPAND_ONE 1 +#define TEGRA30_AUDIOCIF_EXPAND_LFSR 2 + +#define TEGRA30_AUDIOCIF_CTRL_EXPAND_SHIFT 6 +#define TEGRA30_AUDIOCIF_CTRL_EXPAND_MASK (3 << TEGRA30_AUDIOCIF_CTRL_EXPAND_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_EXPAND_ZERO (TEGRA30_AUDIOCIF_EXPAND_ZERO << TEGRA30_AUDIOCIF_CTRL_EXPAND_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_EXPAND_ONE (TEGRA30_AUDIOCIF_EXPAND_ONE << TEGRA30_AUDIOCIF_CTRL_EXPAND_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_EXPAND_LFSR (TEGRA30_AUDIOCIF_EXPAND_LFSR << TEGRA30_AUDIOCIF_CTRL_EXPAND_SHIFT) + +#define TEGRA30_AUDIOCIF_STEREO_CONV_CH0 0 +#define TEGRA30_AUDIOCIF_STEREO_CONV_CH1 1 +#define TEGRA30_AUDIOCIF_STEREO_CONV_AVG 2 + +#define TEGRA30_AUDIOCIF_CTRL_STEREO_CONV_SHIFT 4 +#define TEGRA30_AUDIOCIF_CTRL_STEREO_CONV_MASK (3 << TEGRA30_AUDIOCIF_CTRL_STEREO_CONV_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_STEREO_CONV_CH0 (TEGRA30_AUDIOCIF_STEREO_CONV_CH0 << TEGRA30_AUDIOCIF_CTRL_STEREO_CONV_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_STEREO_CONV_CH1 (TEGRA30_AUDIOCIF_STEREO_CONV_CH1 << TEGRA30_AUDIOCIF_CTRL_STEREO_CONV_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_STEREO_CONV_AVG (TEGRA30_AUDIOCIF_STEREO_CONV_AVG << TEGRA30_AUDIOCIF_CTRL_STEREO_CONV_SHIFT) + +#define TEGRA30_AUDIOCIF_CTRL_REPLICATE 3 + +#define TEGRA30_AUDIOCIF_DIRECTION_TX 0 +#define TEGRA30_AUDIOCIF_DIRECTION_RX 1 + +#define TEGRA30_AUDIOCIF_CTRL_DIRECTION_SHIFT 2 +#define TEGRA30_AUDIOCIF_CTRL_DIRECTION_MASK (1 << TEGRA30_AUDIOCIF_CTRL_DIRECTION_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_DIRECTION_TX (TEGRA30_AUDIOCIF_DIRECTION_TX << TEGRA30_AUDIOCIF_CTRL_DIRECTION_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_DIRECTION_RX (TEGRA30_AUDIOCIF_DIRECTION_RX << TEGRA30_AUDIOCIF_CTRL_DIRECTION_SHIFT) + +#define TEGRA30_AUDIOCIF_TRUNCATE_ROUND 0 +#define TEGRA30_AUDIOCIF_TRUNCATE_CHOP 1 + +#define TEGRA30_AUDIOCIF_CTRL_TRUNCATE_SHIFT 1 +#define TEGRA30_AUDIOCIF_CTRL_TRUNCATE_MASK (1 << TEGRA30_AUDIOCIF_CTRL_TRUNCATE_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_TRUNCATE_ROUND (TEGRA30_AUDIOCIF_TRUNCATE_ROUND << TEGRA30_AUDIOCIF_CTRL_TRUNCATE_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_TRUNCATE_CHOP (TEGRA30_AUDIOCIF_TRUNCATE_CHOP << TEGRA30_AUDIOCIF_CTRL_TRUNCATE_SHIFT) + +#define TEGRA30_AUDIOCIF_MONO_CONV_ZERO 0 +#define TEGRA30_AUDIOCIF_MONO_CONV_COPY 1 + +#define TEGRA30_AUDIOCIF_CTRL_MONO_CONV_SHIFT 0 +#define TEGRA30_AUDIOCIF_CTRL_MONO_CONV_MASK (1 << TEGRA30_AUDIOCIF_CTRL_MONO_CONV_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_MONO_CONV_ZERO (TEGRA30_AUDIOCIF_MONO_CONV_ZERO << TEGRA30_AUDIOCIF_CTRL_MONO_CONV_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_MONO_CONV_COPY (TEGRA30_AUDIOCIF_MONO_CONV_COPY << TEGRA30_AUDIOCIF_CTRL_MONO_CONV_SHIFT) + +/* Registers within TEGRA30_AUDIO_CLUSTER_BASE */ + +/* TEGRA30_AHUB_CHANNEL_CTRL */ + +#define TEGRA30_AHUB_CHANNEL_CTRL 0x0 +#define TEGRA30_AHUB_CHANNEL_CTRL_STRIDE 0x20 +#define TEGRA30_AHUB_CHANNEL_CTRL_COUNT 4 +#define TEGRA30_AHUB_CHANNEL_CTRL_TX_EN (1 << 31) +#define TEGRA30_AHUB_CHANNEL_CTRL_RX_EN (1 << 30) +#define TEGRA30_AHUB_CHANNEL_CTRL_LOOPBACK (1 << 29) + +#define TEGRA30_AHUB_CHANNEL_CTRL_TX_THRESHOLD_SHIFT 16 +#define TEGRA30_AHUB_CHANNEL_CTRL_TX_THRESHOLD_MASK_US 0xff +#define TEGRA30_AHUB_CHANNEL_CTRL_TX_THRESHOLD_MASK (TEGRA30_AHUB_CHANNEL_CTRL_TX_THRESHOLD_MASK_US << TEGRA30_AHUB_CHANNEL_CTRL_TX_THRESHOLD_SHIFT) + +#define TEGRA30_AHUB_CHANNEL_CTRL_RX_THRESHOLD_SHIFT 8 +#define TEGRA30_AHUB_CHANNEL_CTRL_RX_THRESHOLD_MASK_US 0xff +#define TEGRA30_AHUB_CHANNEL_CTRL_RX_THRESHOLD_MASK (TEGRA30_AHUB_CHANNEL_CTRL_RX_THRESHOLD_MASK_US << TEGRA30_AHUB_CHANNEL_CTRL_RX_THRESHOLD_SHIFT) + +#define TEGRA30_AHUB_CHANNEL_CTRL_TX_PACK_EN (1 << 6) + +#define TEGRA30_PACK_8_4 2 +#define TEGRA30_PACK_16 3 + +#define TEGRA30_AHUB_CHANNEL_CTRL_TX_PACK_SHIFT 4 +#define TEGRA30_AHUB_CHANNEL_CTRL_TX_PACK_MASK_US 3 +#define TEGRA30_AHUB_CHANNEL_CTRL_TX_PACK_MASK (TEGRA30_AHUB_CHANNEL_CTRL_TX_PACK_MASK_US << TEGRA30_AHUB_CHANNEL_CTRL_TX_PACK_SHIFT) +#define TEGRA30_AHUB_CHANNEL_CTRL_TX_PACK_8_4 (TEGRA30_PACK_8_4 << TEGRA30_AHUB_CHANNEL_CTRL_TX_PACK_SHIFT) +#define TEGRA30_AHUB_CHANNEL_CTRL_TX_PACK_16 (TEGRA30_PACK_16 << TEGRA30_AHUB_CHANNEL_CTRL_TX_PACK_SHIFT) + +#define TEGRA30_AHUB_CHANNEL_CTRL_RX_PACK_EN (1 << 2) + +#define TEGRA30_AHUB_CHANNEL_CTRL_RX_PACK_SHIFT 0 +#define TEGRA30_AHUB_CHANNEL_CTRL_RX_PACK_MASK_US 3 +#define TEGRA30_AHUB_CHANNEL_CTRL_RX_PACK_MASK (TEGRA30_AHUB_CHANNEL_CTRL_RX_PACK_MASK_US << TEGRA30_AHUB_CHANNEL_CTRL_RX_PACK_SHIFT) +#define TEGRA30_AHUB_CHANNEL_CTRL_RX_PACK_8_4 (TEGRA30_PACK_8_4 << TEGRA30_AHUB_CHANNEL_CTRL_RX_PACK_SHIFT) +#define TEGRA30_AHUB_CHANNEL_CTRL_RX_PACK_16 (TEGRA30_PACK_16 << TEGRA30_AHUB_CHANNEL_CTRL_RX_PACK_SHIFT) + +/* TEGRA30_AHUB_CHANNEL_CLEAR */ + +#define TEGRA30_AHUB_CHANNEL_CLEAR 0x4 +#define TEGRA30_AHUB_CHANNEL_CLEAR_STRIDE 0x20 +#define TEGRA30_AHUB_CHANNEL_CLEAR_COUNT 4 +#define TEGRA30_AHUB_CHANNEL_CLEAR_TX_SOFT_RESET (1 << 31) +#define TEGRA30_AHUB_CHANNEL_CLEAR_RX_SOFT_RESET (1 << 30) + +/* TEGRA30_AHUB_CHANNEL_STATUS */ + +#define TEGRA30_AHUB_CHANNEL_STATUS 0x8 +#define TEGRA30_AHUB_CHANNEL_STATUS_STRIDE 0x20 +#define TEGRA30_AHUB_CHANNEL_STATUS_COUNT 4 +#define TEGRA30_AHUB_CHANNEL_STATUS_TX_FREE_SHIFT 24 +#define TEGRA30_AHUB_CHANNEL_STATUS_TX_FREE_MASK_US 0xff +#define TEGRA30_AHUB_CHANNEL_STATUS_TX_FREE_MASK (TEGRA30_AHUB_CHANNEL_STATUS_TX_FREE_MASK_US << TEGRA30_AHUB_CHANNEL_STATUS_TX_FREE_SHIFT) +#define TEGRA30_AHUB_CHANNEL_STATUS_RX_FREE_SHIFT 16 +#define TEGRA30_AHUB_CHANNEL_STATUS_RX_FREE_MASK_US 0xff +#define TEGRA30_AHUB_CHANNEL_STATUS_RX_FREE_MASK (TEGRA30_AHUB_CHANNEL_STATUS_RX_FREE_MASK_US << TEGRA30_AHUB_CHANNEL_STATUS_RX_FREE_SHIFT) +#define TEGRA30_AHUB_CHANNEL_STATUS_TX_TRIG (1 << 1) +#define TEGRA30_AHUB_CHANNEL_STATUS_RX_TRIG (1 << 0) + +/* TEGRA30_AHUB_CHANNEL_TXFIFO */ + +#define TEGRA30_AHUB_CHANNEL_TXFIFO 0xc +#define TEGRA30_AHUB_CHANNEL_TXFIFO_STRIDE 0x20 +#define TEGRA30_AHUB_CHANNEL_TXFIFO_COUNT 4 + +/* TEGRA30_AHUB_CHANNEL_RXFIFO */ + +#define TEGRA30_AHUB_CHANNEL_RXFIFO 0x10 +#define TEGRA30_AHUB_CHANNEL_RXFIFO_STRIDE 0x20 +#define TEGRA30_AHUB_CHANNEL_RXFIFO_COUNT 4 + +/* TEGRA30_AHUB_CIF_TX_CTRL */ + +#define TEGRA30_AHUB_CIF_TX_CTRL 0x14 +#define TEGRA30_AHUB_CIF_TX_CTRL_STRIDE 0x20 +#define TEGRA30_AHUB_CIF_TX_CTRL_COUNT 4 +/* Uses field from TEGRA30_AUDIOCIF_CTRL_* */ + +/* TEGRA30_AHUB_CIF_RX_CTRL */ + +#define TEGRA30_AHUB_CIF_RX_CTRL 0x18 +#define TEGRA30_AHUB_CIF_RX_CTRL_STRIDE 0x20 +#define TEGRA30_AHUB_CIF_RX_CTRL_COUNT 4 +/* Uses field from TEGRA30_AUDIOCIF_CTRL_* */ + +/* TEGRA30_AHUB_CONFIG_LINK_CTRL */ + +#define TEGRA30_AHUB_CONFIG_LINK_CTRL 0x80 +#define TEGRA30_AHUB_CONFIG_LINK_CTRL_MASTER_FIFO_FULL_CNT_SHIFT 28 +#define TEGRA30_AHUB_CONFIG_LINK_CTRL_MASTER_FIFO_FULL_CNT_MASK_US 0xf +#define TEGRA30_AHUB_CONFIG_LINK_CTRL_MASTER_FIFO_FULL_CNT_MASK (TEGRA30_AHUB_CONFIG_LINK_CTRL_MASTER_FIFO_FULL_CNT_MASK_US << TEGRA30_AHUB_CONFIG_LINK_CTRL_MASTER_FIFO_FULL_CNT_SHIFT) +#define TEGRA30_AHUB_CONFIG_LINK_CTRL_TIMEOUT_CNT_SHIFT 16 +#define TEGRA30_AHUB_CONFIG_LINK_CTRL_TIMEOUT_CNT_MASK_US 0xfff +#define TEGRA30_AHUB_CONFIG_LINK_CTRL_TIMEOUT_CNT_MASK (TEGRA30_AHUB_CONFIG_LINK_CTRL_TIMEOUT_CNT_MASK_US << TEGRA30_AHUB_CONFIG_LINK_CTRL_TIMEOUT_CNT_SHIFT) +#define TEGRA30_AHUB_CONFIG_LINK_CTRL_IDLE_CNT_SHIFT 4 +#define TEGRA30_AHUB_CONFIG_LINK_CTRL_IDLE_CNT_MASK_US 0xfff +#define TEGRA30_AHUB_CONFIG_LINK_CTRL_IDLE_CNT_MASK (TEGRA30_AHUB_CONFIG_LINK_CTRL_IDLE_CNT_MASK_US << TEGRA30_AHUB_CONFIG_LINK_CTRL_IDLE_CNT_SHIFT) +#define TEGRA30_AHUB_CONFIG_LINK_CTRL_CG_EN (1 << 2) +#define TEGRA30_AHUB_CONFIG_LINK_CTRL_CLEAR_TIMEOUT_CNTR (1 << 1) +#define TEGRA30_AHUB_CONFIG_LINK_CTRL_SOFT_RESET (1 << 0) + +/* TEGRA30_AHUB_MISC_CTRL */ + +#define TEGRA30_AHUB_MISC_CTRL 0x84 +#define TEGRA30_AHUB_MISC_CTRL_AUDIO_ACTIVE (1 << 31) +#define TEGRA30_AHUB_MISC_CTRL_AUDIO_CG_EN (1 << 8) +#define TEGRA30_AHUB_MISC_CTRL_AUDIO_OBS_SEL_SHIFT 0 +#define TEGRA30_AHUB_MISC_CTRL_AUDIO_OBS_SEL_MASK (0x1f << TEGRA30_AHUB_MISC_CTRL_AUDIO_OBS_SEL_SHIFT) + +/* TEGRA30_AHUB_APBDMA_LIVE_STATUS */ + +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS 0x88 +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH3_RX_CIF_FIFO_FULL (1 << 31) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH3_TX_CIF_FIFO_FULL (1 << 30) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH2_RX_CIF_FIFO_FULL (1 << 29) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH2_TX_CIF_FIFO_FULL (1 << 28) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH1_RX_CIF_FIFO_FULL (1 << 27) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH1_TX_CIF_FIFO_FULL (1 << 26) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH0_RX_CIF_FIFO_FULL (1 << 25) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH0_TX_CIF_FIFO_FULL (1 << 24) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH3_RX_CIF_FIFO_EMPTY (1 << 23) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH3_TX_CIF_FIFO_EMPTY (1 << 22) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH2_RX_CIF_FIFO_EMPTY (1 << 21) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH2_TX_CIF_FIFO_EMPTY (1 << 20) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH1_RX_CIF_FIFO_EMPTY (1 << 19) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH1_TX_CIF_FIFO_EMPTY (1 << 18) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH0_RX_CIF_FIFO_EMPTY (1 << 17) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH0_TX_CIF_FIFO_EMPTY (1 << 16) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH3_RX_DMA_FIFO_FULL (1 << 15) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH3_TX_DMA_FIFO_FULL (1 << 14) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH2_RX_DMA_FIFO_FULL (1 << 13) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH2_TX_DMA_FIFO_FULL (1 << 12) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH1_RX_DMA_FIFO_FULL (1 << 11) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH1_TX_DMA_FIFO_FULL (1 << 10) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH0_RX_DMA_FIFO_FULL (1 << 9) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH0_TX_DMA_FIFO_FULL (1 << 8) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH3_RX_DMA_FIFO_EMPTY (1 << 7) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH3_TX_DMA_FIFO_EMPTY (1 << 6) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH2_RX_DMA_FIFO_EMPTY (1 << 5) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH2_TX_DMA_FIFO_EMPTY (1 << 4) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH1_RX_DMA_FIFO_EMPTY (1 << 3) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH1_TX_DMA_FIFO_EMPTY (1 << 2) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH0_RX_DMA_FIFO_EMPTY (1 << 1) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH0_TX_DMA_FIFO_EMPTY (1 << 0) + +/* TEGRA30_AHUB_I2S_LIVE_STATUS */ + +#define TEGRA30_AHUB_I2S_LIVE_STATUS 0x8c +#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S4_RX_FIFO_FULL (1 << 29) +#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S4_TX_FIFO_FULL (1 << 28) +#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S3_RX_FIFO_FULL (1 << 27) +#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S3_TX_FIFO_FULL (1 << 26) +#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S2_RX_FIFO_FULL (1 << 25) +#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S2_TX_FIFO_FULL (1 << 24) +#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S1_RX_FIFO_FULL (1 << 23) +#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S1_TX_FIFO_FULL (1 << 22) +#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S0_RX_FIFO_FULL (1 << 21) +#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S0_TX_FIFO_FULL (1 << 20) +#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S4_RX_FIFO_ENABLED (1 << 19) +#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S4_TX_FIFO_ENABLED (1 << 18) +#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S3_RX_FIFO_ENABLED (1 << 17) +#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S3_TX_FIFO_ENABLED (1 << 16) +#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S2_RX_FIFO_ENABLED (1 << 15) +#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S2_TX_FIFO_ENABLED (1 << 14) +#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S1_RX_FIFO_ENABLED (1 << 13) +#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S1_TX_FIFO_ENABLED (1 << 12) +#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S0_RX_FIFO_ENABLED (1 << 11) +#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S0_TX_FIFO_ENABLED (1 << 10) +#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S4_RX_FIFO_EMPTY (1 << 9) +#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S4_TX_FIFO_EMPTY (1 << 8) +#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S3_RX_FIFO_EMPTY (1 << 7) +#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S3_TX_FIFO_EMPTY (1 << 6) +#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S2_RX_FIFO_EMPTY (1 << 5) +#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S2_TX_FIFO_EMPTY (1 << 4) +#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S1_RX_FIFO_EMPTY (1 << 3) +#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S1_TX_FIFO_EMPTY (1 << 2) +#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S0_RX_FIFO_EMPTY (1 << 1) +#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S0_TX_FIFO_EMPTY (1 << 0) + +/* TEGRA30_AHUB_DAM0_LIVE_STATUS */ + +#define TEGRA30_AHUB_DAM_LIVE_STATUS 0x90 +#define TEGRA30_AHUB_DAM_LIVE_STATUS_STRIDE 0x8 +#define TEGRA30_AHUB_DAM_LIVE_STATUS_COUNT 3 +#define TEGRA30_AHUB_DAM_LIVE_STATUS_TX_ENABLED (1 << 26) +#define TEGRA30_AHUB_DAM_LIVE_STATUS_RX1_ENABLED (1 << 25) +#define TEGRA30_AHUB_DAM_LIVE_STATUS_RX0_ENABLED (1 << 24) +#define TEGRA30_AHUB_DAM_LIVE_STATUS_TXFIFO_FULL (1 << 15) +#define TEGRA30_AHUB_DAM_LIVE_STATUS_RX1FIFO_FULL (1 << 9) +#define TEGRA30_AHUB_DAM_LIVE_STATUS_RX0FIFO_FULL (1 << 8) +#define TEGRA30_AHUB_DAM_LIVE_STATUS_TXFIFO_EMPTY (1 << 7) +#define TEGRA30_AHUB_DAM_LIVE_STATUS_RX1FIFO_EMPTY (1 << 1) +#define TEGRA30_AHUB_DAM_LIVE_STATUS_RX0FIFO_EMPTY (1 << 0) + +/* TEGRA30_AHUB_SPDIF_LIVE_STATUS */ + +#define TEGRA30_AHUB_SPDIF_LIVE_STATUS 0xa8 +#define TEGRA30_AHUB_SPDIF_LIVE_STATUS_USER_TX_ENABLED (1 << 11) +#define TEGRA30_AHUB_SPDIF_LIVE_STATUS_USER_RX_ENABLED (1 << 10) +#define TEGRA30_AHUB_SPDIF_LIVE_STATUS_DATA_TX_ENABLED (1 << 9) +#define TEGRA30_AHUB_SPDIF_LIVE_STATUS_DATA_RX_ENABLED (1 << 8) +#define TEGRA30_AHUB_SPDIF_LIVE_STATUS_USER_TXFIFO_FULL (1 << 7) +#define TEGRA30_AHUB_SPDIF_LIVE_STATUS_USER_RXFIFO_FULL (1 << 6) +#define TEGRA30_AHUB_SPDIF_LIVE_STATUS_DATA_TXFIFO_FULL (1 << 5) +#define TEGRA30_AHUB_SPDIF_LIVE_STATUS_DATA_RXFIFO_FULL (1 << 4) +#define TEGRA30_AHUB_SPDIF_LIVE_STATUS_USER_TXFIFO_EMPTY (1 << 3) +#define TEGRA30_AHUB_SPDIF_LIVE_STATUS_USER_RXFIFO_EMPTY (1 << 2) +#define TEGRA30_AHUB_SPDIF_LIVE_STATUS_DATA_TXFIFO_EMPTY (1 << 1) +#define TEGRA30_AHUB_SPDIF_LIVE_STATUS_DATA_RXFIFO_EMPTY (1 << 0) + +/* TEGRA30_AHUB_I2S_INT_MASK */ + +#define TEGRA30_AHUB_I2S_INT_MASK 0xb0 + +/* TEGRA30_AHUB_DAM_INT_MASK */ + +#define TEGRA30_AHUB_DAM_INT_MASK 0xb4 + +/* TEGRA30_AHUB_SPDIF_INT_MASK */ + +#define TEGRA30_AHUB_SPDIF_INT_MASK 0xbc + +/* TEGRA30_AHUB_APBIF_INT_MASK */ + +#define TEGRA30_AHUB_APBIF_INT_MASK 0xc0 + +/* TEGRA30_AHUB_I2S_INT_STATUS */ + +#define TEGRA30_AHUB_I2S_INT_STATUS 0xc8 + +/* TEGRA30_AHUB_DAM_INT_STATUS */ + +#define TEGRA30_AHUB_DAM_INT_STATUS 0xcc + +/* TEGRA30_AHUB_SPDIF_INT_STATUS */ + +#define TEGRA30_AHUB_SPDIF_INT_STATUS 0xd4 + +/* TEGRA30_AHUB_APBIF_INT_STATUS */ + +#define TEGRA30_AHUB_APBIF_INT_STATUS 0xd8 + +/* TEGRA30_AHUB_I2S_INT_SOURCE */ + +#define TEGRA30_AHUB_I2S_INT_SOURCE 0xe0 + +/* TEGRA30_AHUB_DAM_INT_SOURCE */ + +#define TEGRA30_AHUB_DAM_INT_SOURCE 0xe4 + +/* TEGRA30_AHUB_SPDIF_INT_SOURCE */ + +#define TEGRA30_AHUB_SPDIF_INT_SOURCE 0xec + +/* TEGRA30_AHUB_APBIF_INT_SOURCE */ + +#define TEGRA30_AHUB_APBIF_INT_SOURCE 0xf0 + +/* TEGRA30_AHUB_I2S_INT_SET */ + +#define TEGRA30_AHUB_I2S_INT_SET 0xf8 + +/* TEGRA30_AHUB_DAM_INT_SET */ + +#define TEGRA30_AHUB_DAM_INT_SET 0xfc + +/* TEGRA30_AHUB_SPDIF_INT_SET */ + +#define TEGRA30_AHUB_SPDIF_INT_SET 0x100 + +/* TEGRA30_AHUB_APBIF_INT_SET */ + +#define TEGRA30_AHUB_APBIF_INT_SET 0x104 + +/* Registers within TEGRA30_AHUB_BASE */ + +#define TEGRA30_AHUB_AUDIO_RX 0x0 +#define TEGRA30_AHUB_AUDIO_RX_STRIDE 0x4 +#define TEGRA30_AHUB_AUDIO_RX_COUNT 17 +/* This register repeats once for each entry in enum tegra30_ahub_rxcif */ +/* The fields in this register are 1 bit per entry in tegra30_ahub_txcif */ + +/* + * Terminology: + * AHUB: Audio Hub; a cross-bar switch between the audio devices: DMA FIFOs, + * I2S controllers, SPDIF controllers, and DAMs. + * XBAR: The core cross-bar component of the AHUB. + * CIF: Client Interface; the HW module connecting an audio device to the + * XBAR. + * DAM: Digital Audio Mixer: A HW module that mixes multiple audio streams, + * possibly including sample-rate conversion. + * + * Each TX CIF transmits data into the XBAR. Each RX CIF can receive audio + * transmitted by a particular TX CIF. + * + * This driver is currently very simplistic; many HW features are not + * exposed; DAMs are not supported, only 16-bit stereo audio is supported, + * etc. + */ + +enum tegra30_ahub_txcif { + TEGRA30_AHUB_TXCIF_APBIF_TX0, + TEGRA30_AHUB_TXCIF_APBIF_TX1, + TEGRA30_AHUB_TXCIF_APBIF_TX2, + TEGRA30_AHUB_TXCIF_APBIF_TX3, + TEGRA30_AHUB_TXCIF_I2S0_TX0, + TEGRA30_AHUB_TXCIF_I2S1_TX0, + TEGRA30_AHUB_TXCIF_I2S2_TX0, + TEGRA30_AHUB_TXCIF_I2S3_TX0, + TEGRA30_AHUB_TXCIF_I2S4_TX0, + TEGRA30_AHUB_TXCIF_DAM0_TX0, + TEGRA30_AHUB_TXCIF_DAM1_TX0, + TEGRA30_AHUB_TXCIF_DAM2_TX0, + TEGRA30_AHUB_TXCIF_SPDIF_TX0, + TEGRA30_AHUB_TXCIF_SPDIF_TX1, +}; + +enum tegra30_ahub_rxcif { + TEGRA30_AHUB_RXCIF_APBIF_RX0, + TEGRA30_AHUB_RXCIF_APBIF_RX1, + TEGRA30_AHUB_RXcIF_APBIF_RX2, + TEGRA30_AHUB_RXCIF_APBIF_RX3, + TEGRA30_AHUB_RXCIF_I2S0_RX0, + TEGRA30_AHUB_RXCIF_I2S1_RX0, + TEGRA30_AHUB_RXCIF_I2S2_RX0, + TEGRA30_AHUB_RXCIF_I2S3_RX0, + TEGRA30_AHUB_RXCIF_I2S4_RX0, + TEGRA30_AHUB_RXCIF_DAM0_RX0, + TEGRA30_AHUB_RXCIF_DAM0_RX1, + TEGRA30_AHUB_RXCIF_DAM1_RX0, + TEGRA30_AHUB_RXCIF_DAM2_RX1, + TEGRA30_AHUB_RXCIF_DAM3_RX0, + TEGRA30_AHUB_RXCIF_DAM3_RX1, + TEGRA30_AHUB_RXCIF_SPDIF_RX0, + TEGRA30_AHUB_RXCIF_SPDIF_RX1, +}; + +extern int tegra30_ahub_allocate_rx_fifo(enum tegra30_ahub_rxcif *rxcif, + unsigned long *fiforeg, + unsigned long *reqsel); +extern int tegra30_ahub_enable_rx_fifo(enum tegra30_ahub_rxcif rxcif); +extern int tegra30_ahub_disable_rx_fifo(enum tegra30_ahub_rxcif rxcif); +extern int tegra30_ahub_free_rx_fifo(enum tegra30_ahub_rxcif rxcif); + +extern int tegra30_ahub_allocate_tx_fifo(enum tegra30_ahub_txcif *txcif, + unsigned long *fiforeg, + unsigned long *reqsel); +extern int tegra30_ahub_enable_tx_fifo(enum tegra30_ahub_txcif txcif); +extern int tegra30_ahub_disable_tx_fifo(enum tegra30_ahub_txcif txcif); +extern int tegra30_ahub_free_tx_fifo(enum tegra30_ahub_txcif txcif); + +extern int tegra30_ahub_set_rx_cif_source(enum tegra30_ahub_rxcif rxcif, + enum tegra30_ahub_txcif txcif); +extern int tegra30_ahub_unset_rx_cif_source(enum tegra30_ahub_rxcif rxcif); + +struct tegra30_ahub { + struct device *dev; + struct clk *clk_d_audio; + struct clk *clk_apbif; + int dma_sel; + resource_size_t apbif_addr; + struct regmap *regmap_apbif; + struct regmap *regmap_ahub; + DECLARE_BITMAP(rx_usage, TEGRA30_AHUB_CHANNEL_CTRL_COUNT); + DECLARE_BITMAP(tx_usage, TEGRA30_AHUB_CHANNEL_CTRL_COUNT); +}; + +#endif diff --git a/sound/soc/tegra/tegra30_i2s.c b/sound/soc/tegra/tegra30_i2s.c new file mode 100644 index 00000000000..8596032985d --- /dev/null +++ b/sound/soc/tegra/tegra30_i2s.c @@ -0,0 +1,536 @@ +/* + * tegra30_i2s.c - Tegra30 I2S driver + * + * Author: Stephen Warren <swarren@nvidia.com> + * Copyright (c) 2010-2012, NVIDIA CORPORATION. All rights reserved. + * + * Based on code copyright/by: + * + * Copyright (c) 2009-2010, NVIDIA Corporation. + * Scott Peterson <speterson@nvidia.com> + * + * Copyright (C) 2010 Google, Inc. + * Iliyan Malchev <malchev@google.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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/clk.h> +#include <linux/device.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <linux/slab.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> + +#include "tegra30_ahub.h" +#include "tegra30_i2s.h" + +#define DRV_NAME "tegra30-i2s" + +static inline void tegra30_i2s_write(struct tegra30_i2s *i2s, u32 reg, u32 val) +{ + regmap_write(i2s->regmap, reg, val); +} + +static inline u32 tegra30_i2s_read(struct tegra30_i2s *i2s, u32 reg) +{ + u32 val; + regmap_read(i2s->regmap, reg, &val); + return val; +} + +static int tegra30_i2s_runtime_suspend(struct device *dev) +{ + struct tegra30_i2s *i2s = dev_get_drvdata(dev); + + regcache_cache_only(i2s->regmap, true); + + clk_disable(i2s->clk_i2s); + + return 0; +} + +static int tegra30_i2s_runtime_resume(struct device *dev) +{ + struct tegra30_i2s *i2s = dev_get_drvdata(dev); + int ret; + + ret = clk_enable(i2s->clk_i2s); + if (ret) { + dev_err(dev, "clk_enable failed: %d\n", ret); + return ret; + } + + regcache_cache_only(i2s->regmap, false); + + return 0; +} + +int tegra30_i2s_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct tegra30_i2s *i2s = snd_soc_dai_get_drvdata(dai); + int ret; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + ret = tegra30_ahub_allocate_tx_fifo(&i2s->playback_fifo_cif, + &i2s->playback_dma_data.addr, + &i2s->playback_dma_data.req_sel); + i2s->playback_dma_data.wrap = 4; + i2s->playback_dma_data.width = 32; + tegra30_ahub_set_rx_cif_source(i2s->playback_i2s_cif, + i2s->playback_fifo_cif); + } else { + ret = tegra30_ahub_allocate_rx_fifo(&i2s->capture_fifo_cif, + &i2s->capture_dma_data.addr, + &i2s->capture_dma_data.req_sel); + i2s->capture_dma_data.wrap = 4; + i2s->capture_dma_data.width = 32; + tegra30_ahub_set_rx_cif_source(i2s->capture_fifo_cif, + i2s->capture_i2s_cif); + } + + return ret; +} + +void tegra30_i2s_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct tegra30_i2s *i2s = snd_soc_dai_get_drvdata(dai); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + tegra30_ahub_unset_rx_cif_source(i2s->playback_i2s_cif); + tegra30_ahub_free_tx_fifo(i2s->playback_fifo_cif); + } else { + tegra30_ahub_unset_rx_cif_source(i2s->capture_fifo_cif); + tegra30_ahub_free_rx_fifo(i2s->capture_fifo_cif); + } +} + +static int tegra30_i2s_set_fmt(struct snd_soc_dai *dai, + unsigned int fmt) +{ + struct tegra30_i2s *i2s = snd_soc_dai_get_drvdata(dai); + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + default: + return -EINVAL; + } + + i2s->reg_ctrl &= ~TEGRA30_I2S_CTRL_MASTER_ENABLE; + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + i2s->reg_ctrl |= TEGRA30_I2S_CTRL_MASTER_ENABLE; + break; + case SND_SOC_DAIFMT_CBM_CFM: + break; + default: + return -EINVAL; + } + + i2s->reg_ctrl &= ~(TEGRA30_I2S_CTRL_FRAME_FORMAT_MASK | + TEGRA30_I2S_CTRL_LRCK_MASK); + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_A: + i2s->reg_ctrl |= TEGRA30_I2S_CTRL_FRAME_FORMAT_FSYNC; + i2s->reg_ctrl |= TEGRA30_I2S_CTRL_LRCK_L_LOW; + break; + case SND_SOC_DAIFMT_DSP_B: + i2s->reg_ctrl |= TEGRA30_I2S_CTRL_FRAME_FORMAT_FSYNC; + i2s->reg_ctrl |= TEGRA30_I2S_CTRL_LRCK_R_LOW; + break; + case SND_SOC_DAIFMT_I2S: + i2s->reg_ctrl |= TEGRA30_I2S_CTRL_FRAME_FORMAT_LRCK; + i2s->reg_ctrl |= TEGRA30_I2S_CTRL_LRCK_L_LOW; + break; + case SND_SOC_DAIFMT_RIGHT_J: + i2s->reg_ctrl |= TEGRA30_I2S_CTRL_FRAME_FORMAT_LRCK; + i2s->reg_ctrl |= TEGRA30_I2S_CTRL_LRCK_L_LOW; + break; + case SND_SOC_DAIFMT_LEFT_J: + i2s->reg_ctrl |= TEGRA30_I2S_CTRL_FRAME_FORMAT_LRCK; + i2s->reg_ctrl |= TEGRA30_I2S_CTRL_LRCK_L_LOW; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int tegra30_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct device *dev = substream->pcm->card->dev; + struct tegra30_i2s *i2s = snd_soc_dai_get_drvdata(dai); + u32 val; + int ret, sample_size, srate, i2sclock, bitcnt; + + if (params_channels(params) != 2) + return -EINVAL; + + i2s->reg_ctrl &= ~TEGRA30_I2S_CTRL_BIT_SIZE_MASK; + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + i2s->reg_ctrl |= TEGRA30_I2S_CTRL_BIT_SIZE_16; + sample_size = 16; + break; + default: + return -EINVAL; + } + + srate = params_rate(params); + + /* Final "* 2" required by Tegra hardware */ + i2sclock = srate * params_channels(params) * sample_size * 2; + + bitcnt = (i2sclock / (2 * srate)) - 1; + if (bitcnt < 0 || bitcnt > TEGRA30_I2S_TIMING_CHANNEL_BIT_COUNT_MASK_US) + return -EINVAL; + + ret = clk_set_rate(i2s->clk_i2s, i2sclock); + if (ret) { + dev_err(dev, "Can't set I2S clock rate: %d\n", ret); + return ret; + } + + val = bitcnt << TEGRA30_I2S_TIMING_CHANNEL_BIT_COUNT_SHIFT; + + if (i2sclock % (2 * srate)) + val |= TEGRA30_I2S_TIMING_NON_SYM_ENABLE; + + tegra30_i2s_write(i2s, TEGRA30_I2S_TIMING, val); + + val = (0 << TEGRA30_AUDIOCIF_CTRL_FIFO_THRESHOLD_SHIFT) | + (1 << TEGRA30_AUDIOCIF_CTRL_AUDIO_CHANNELS_SHIFT) | + (1 << TEGRA30_AUDIOCIF_CTRL_CLIENT_CHANNELS_SHIFT) | + TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_16 | + TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_16; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + val |= TEGRA30_AUDIOCIF_CTRL_DIRECTION_RX; + tegra30_i2s_write(i2s, TEGRA30_I2S_CIF_RX_CTRL, val); + } else { + val |= TEGRA30_AUDIOCIF_CTRL_DIRECTION_TX; + tegra30_i2s_write(i2s, TEGRA30_I2S_CIF_TX_CTRL, val); + } + + val = (1 << TEGRA30_I2S_OFFSET_RX_DATA_OFFSET_SHIFT) | + (1 << TEGRA30_I2S_OFFSET_TX_DATA_OFFSET_SHIFT); + tegra30_i2s_write(i2s, TEGRA30_I2S_OFFSET, val); + + return 0; +} + +static void tegra30_i2s_start_playback(struct tegra30_i2s *i2s) +{ + tegra30_ahub_enable_tx_fifo(i2s->playback_fifo_cif); + i2s->reg_ctrl |= TEGRA30_I2S_CTRL_XFER_EN_TX; + tegra30_i2s_write(i2s, TEGRA30_I2S_CTRL, i2s->reg_ctrl); +} + +static void tegra30_i2s_stop_playback(struct tegra30_i2s *i2s) +{ + tegra30_ahub_disable_tx_fifo(i2s->playback_fifo_cif); + i2s->reg_ctrl &= ~TEGRA30_I2S_CTRL_XFER_EN_TX; + tegra30_i2s_write(i2s, TEGRA30_I2S_CTRL, i2s->reg_ctrl); +} + +static void tegra30_i2s_start_capture(struct tegra30_i2s *i2s) +{ + tegra30_ahub_enable_rx_fifo(i2s->capture_fifo_cif); + i2s->reg_ctrl |= TEGRA30_I2S_CTRL_XFER_EN_RX; + tegra30_i2s_write(i2s, TEGRA30_I2S_CTRL, i2s->reg_ctrl); +} + +static void tegra30_i2s_stop_capture(struct tegra30_i2s *i2s) +{ + tegra30_ahub_disable_rx_fifo(i2s->capture_fifo_cif); + i2s->reg_ctrl &= ~TEGRA30_I2S_CTRL_XFER_EN_RX; + tegra30_i2s_write(i2s, TEGRA30_I2S_CTRL, i2s->reg_ctrl); +} + +static int tegra30_i2s_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct tegra30_i2s *i2s = snd_soc_dai_get_drvdata(dai); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + case SNDRV_PCM_TRIGGER_RESUME: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + tegra30_i2s_start_playback(i2s); + else + tegra30_i2s_start_capture(i2s); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_SUSPEND: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + tegra30_i2s_stop_playback(i2s); + else + tegra30_i2s_stop_capture(i2s); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int tegra30_i2s_probe(struct snd_soc_dai *dai) +{ + struct tegra30_i2s *i2s = snd_soc_dai_get_drvdata(dai); + + dai->capture_dma_data = &i2s->capture_dma_data; + dai->playback_dma_data = &i2s->playback_dma_data; + + return 0; +} + +static struct snd_soc_dai_ops tegra30_i2s_dai_ops = { + .startup = tegra30_i2s_startup, + .shutdown = tegra30_i2s_shutdown, + .set_fmt = tegra30_i2s_set_fmt, + .hw_params = tegra30_i2s_hw_params, + .trigger = tegra30_i2s_trigger, +}; + +static const struct snd_soc_dai_driver tegra30_i2s_dai_template = { + .probe = tegra30_i2s_probe, + .playback = { + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &tegra30_i2s_dai_ops, + .symmetric_rates = 1, +}; + +static bool tegra30_i2s_wr_rd_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TEGRA30_I2S_CTRL: + case TEGRA30_I2S_TIMING: + case TEGRA30_I2S_OFFSET: + case TEGRA30_I2S_CH_CTRL: + case TEGRA30_I2S_SLOT_CTRL: + case TEGRA30_I2S_CIF_RX_CTRL: + case TEGRA30_I2S_CIF_TX_CTRL: + case TEGRA30_I2S_FLOWCTL: + case TEGRA30_I2S_TX_STEP: + case TEGRA30_I2S_FLOW_STATUS: + case TEGRA30_I2S_FLOW_TOTAL: + case TEGRA30_I2S_FLOW_OVER: + case TEGRA30_I2S_FLOW_UNDER: + case TEGRA30_I2S_LCOEF_1_4_0: + case TEGRA30_I2S_LCOEF_1_4_1: + case TEGRA30_I2S_LCOEF_1_4_2: + case TEGRA30_I2S_LCOEF_1_4_3: + case TEGRA30_I2S_LCOEF_1_4_4: + case TEGRA30_I2S_LCOEF_1_4_5: + case TEGRA30_I2S_LCOEF_2_4_0: + case TEGRA30_I2S_LCOEF_2_4_1: + case TEGRA30_I2S_LCOEF_2_4_2: + return true; + default: + return false; + }; +} + +static bool tegra30_i2s_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TEGRA30_I2S_FLOW_STATUS: + case TEGRA30_I2S_FLOW_TOTAL: + case TEGRA30_I2S_FLOW_OVER: + case TEGRA30_I2S_FLOW_UNDER: + return true; + default: + return false; + }; +} + +static const struct regmap_config tegra30_i2s_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = TEGRA30_I2S_LCOEF_2_4_2, + .writeable_reg = tegra30_i2s_wr_rd_reg, + .readable_reg = tegra30_i2s_wr_rd_reg, + .volatile_reg = tegra30_i2s_volatile_reg, + .cache_type = REGCACHE_RBTREE, +}; + +static __devinit int tegra30_i2s_platform_probe(struct platform_device *pdev) +{ + struct tegra30_i2s *i2s; + u32 cif_ids[2]; + struct resource *mem, *memregion; + void __iomem *regs; + int ret; + + i2s = devm_kzalloc(&pdev->dev, sizeof(struct tegra30_i2s), GFP_KERNEL); + if (!i2s) { + dev_err(&pdev->dev, "Can't allocate tegra30_i2s\n"); + ret = -ENOMEM; + goto err; + } + dev_set_drvdata(&pdev->dev, i2s); + + i2s->dai = tegra30_i2s_dai_template; + i2s->dai.name = dev_name(&pdev->dev); + + ret = of_property_read_u32_array(pdev->dev.of_node, + "nvidia,ahub-cif-ids", cif_ids, + ARRAY_SIZE(cif_ids)); + if (ret < 0) + goto err; + + i2s->playback_i2s_cif = cif_ids[0]; + i2s->capture_i2s_cif = cif_ids[1]; + + i2s->clk_i2s = clk_get(&pdev->dev, NULL); + if (IS_ERR(i2s->clk_i2s)) { + dev_err(&pdev->dev, "Can't retrieve i2s clock\n"); + ret = PTR_ERR(i2s->clk_i2s); + goto err; + } + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!mem) { + dev_err(&pdev->dev, "No memory resource\n"); + ret = -ENODEV; + goto err_clk_put; + } + + memregion = devm_request_mem_region(&pdev->dev, mem->start, + resource_size(mem), DRV_NAME); + if (!memregion) { + dev_err(&pdev->dev, "Memory region already claimed\n"); + ret = -EBUSY; + goto err_clk_put; + } + + regs = devm_ioremap(&pdev->dev, mem->start, resource_size(mem)); + if (!regs) { + dev_err(&pdev->dev, "ioremap failed\n"); + ret = -ENOMEM; + goto err_clk_put; + } + + i2s->regmap = devm_regmap_init_mmio(&pdev->dev, regs, + &tegra30_i2s_regmap_config); + if (IS_ERR(i2s->regmap)) { + dev_err(&pdev->dev, "regmap init failed\n"); + ret = PTR_ERR(i2s->regmap); + goto err_clk_put; + } + regcache_cache_only(i2s->regmap, true); + + pm_runtime_enable(&pdev->dev); + if (!pm_runtime_enabled(&pdev->dev)) { + ret = tegra30_i2s_runtime_resume(&pdev->dev); + if (ret) + goto err_pm_disable; + } + + ret = snd_soc_register_dai(&pdev->dev, &i2s->dai); + if (ret) { + dev_err(&pdev->dev, "Could not register DAI: %d\n", ret); + ret = -ENOMEM; + goto err_suspend; + } + + ret = tegra_pcm_platform_register(&pdev->dev); + if (ret) { + dev_err(&pdev->dev, "Could not register PCM: %d\n", ret); + goto err_unregister_dai; + } + + return 0; + +err_unregister_dai: + snd_soc_unregister_dai(&pdev->dev); +err_suspend: + if (!pm_runtime_status_suspended(&pdev->dev)) + tegra30_i2s_runtime_suspend(&pdev->dev); +err_pm_disable: + pm_runtime_disable(&pdev->dev); +err_clk_put: + clk_put(i2s->clk_i2s); +err: + return ret; +} + +static int __devexit tegra30_i2s_platform_remove(struct platform_device *pdev) +{ + struct tegra30_i2s *i2s = dev_get_drvdata(&pdev->dev); + + pm_runtime_disable(&pdev->dev); + if (!pm_runtime_status_suspended(&pdev->dev)) + tegra30_i2s_runtime_suspend(&pdev->dev); + + tegra_pcm_platform_unregister(&pdev->dev); + snd_soc_unregister_dai(&pdev->dev); + + clk_put(i2s->clk_i2s); + + return 0; +} + +static const struct of_device_id tegra30_i2s_of_match[] __devinitconst = { + { .compatible = "nvidia,tegra30-i2s", }, + {}, +}; + +static const struct dev_pm_ops tegra30_i2s_pm_ops __devinitconst = { + SET_RUNTIME_PM_OPS(tegra30_i2s_runtime_suspend, + tegra30_i2s_runtime_resume, NULL) +}; + +static struct platform_driver tegra30_i2s_driver = { + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + .of_match_table = tegra30_i2s_of_match, + .pm = &tegra30_i2s_pm_ops, + }, + .probe = tegra30_i2s_platform_probe, + .remove = __devexit_p(tegra30_i2s_platform_remove), +}; +module_platform_driver(tegra30_i2s_driver); + +MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>"); +MODULE_DESCRIPTION("Tegra30 I2S ASoC driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRV_NAME); +MODULE_DEVICE_TABLE(of, tegra30_i2s_of_match); diff --git a/sound/soc/tegra/tegra30_i2s.h b/sound/soc/tegra/tegra30_i2s.h new file mode 100644 index 00000000000..91adf29c7a8 --- /dev/null +++ b/sound/soc/tegra/tegra30_i2s.h @@ -0,0 +1,242 @@ +/* + * tegra30_i2s.h - Definitions for Tegra30 I2S driver + * + * Copyright (c) 2011,2012, NVIDIA CORPORATION. All rights reserved. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef __TEGRA30_I2S_H__ +#define __TEGRA30_I2S_H__ + +#include "tegra_pcm.h" + +/* Register offsets from TEGRA30_I2S*_BASE */ + +#define TEGRA30_I2S_CTRL 0x0 +#define TEGRA30_I2S_TIMING 0x4 +#define TEGRA30_I2S_OFFSET 0x08 +#define TEGRA30_I2S_CH_CTRL 0x0c +#define TEGRA30_I2S_SLOT_CTRL 0x10 +#define TEGRA30_I2S_CIF_RX_CTRL 0x14 +#define TEGRA30_I2S_CIF_TX_CTRL 0x18 +#define TEGRA30_I2S_FLOWCTL 0x1c +#define TEGRA30_I2S_TX_STEP 0x20 +#define TEGRA30_I2S_FLOW_STATUS 0x24 +#define TEGRA30_I2S_FLOW_TOTAL 0x28 +#define TEGRA30_I2S_FLOW_OVER 0x2c +#define TEGRA30_I2S_FLOW_UNDER 0x30 +#define TEGRA30_I2S_LCOEF_1_4_0 0x34 +#define TEGRA30_I2S_LCOEF_1_4_1 0x38 +#define TEGRA30_I2S_LCOEF_1_4_2 0x3c +#define TEGRA30_I2S_LCOEF_1_4_3 0x40 +#define TEGRA30_I2S_LCOEF_1_4_4 0x44 +#define TEGRA30_I2S_LCOEF_1_4_5 0x48 +#define TEGRA30_I2S_LCOEF_2_4_0 0x4c +#define TEGRA30_I2S_LCOEF_2_4_1 0x50 +#define TEGRA30_I2S_LCOEF_2_4_2 0x54 + +/* Fields in TEGRA30_I2S_CTRL */ + +#define TEGRA30_I2S_CTRL_XFER_EN_TX (1 << 31) +#define TEGRA30_I2S_CTRL_XFER_EN_RX (1 << 30) +#define TEGRA30_I2S_CTRL_CG_EN (1 << 29) +#define TEGRA30_I2S_CTRL_SOFT_RESET (1 << 28) +#define TEGRA30_I2S_CTRL_TX_FLOWCTL_EN (1 << 27) + +#define TEGRA30_I2S_CTRL_OBS_SEL_SHIFT 24 +#define TEGRA30_I2S_CTRL_OBS_SEL_MASK (7 << TEGRA30_I2S_CTRL_OBS_SEL_SHIFT) + +#define TEGRA30_I2S_FRAME_FORMAT_LRCK 0 +#define TEGRA30_I2S_FRAME_FORMAT_FSYNC 1 + +#define TEGRA30_I2S_CTRL_FRAME_FORMAT_SHIFT 12 +#define TEGRA30_I2S_CTRL_FRAME_FORMAT_MASK (7 << TEGRA30_I2S_CTRL_FRAME_FORMAT_SHIFT) +#define TEGRA30_I2S_CTRL_FRAME_FORMAT_LRCK (TEGRA30_I2S_FRAME_FORMAT_LRCK << TEGRA30_I2S_CTRL_FRAME_FORMAT_SHIFT) +#define TEGRA30_I2S_CTRL_FRAME_FORMAT_FSYNC (TEGRA30_I2S_FRAME_FORMAT_FSYNC << TEGRA30_I2S_CTRL_FRAME_FORMAT_SHIFT) + +#define TEGRA30_I2S_CTRL_MASTER_ENABLE (1 << 10) + +#define TEGRA30_I2S_LRCK_LEFT_LOW 0 +#define TEGRA30_I2S_LRCK_RIGHT_LOW 1 + +#define TEGRA30_I2S_CTRL_LRCK_SHIFT 9 +#define TEGRA30_I2S_CTRL_LRCK_MASK (1 << TEGRA30_I2S_CTRL_LRCK_SHIFT) +#define TEGRA30_I2S_CTRL_LRCK_L_LOW (TEGRA30_I2S_LRCK_LEFT_LOW << TEGRA30_I2S_CTRL_LRCK_SHIFT) +#define TEGRA30_I2S_CTRL_LRCK_R_LOW (TEGRA30_I2S_LRCK_RIGHT_LOW << TEGRA30_I2S_CTRL_LRCK_SHIFT) + +#define TEGRA30_I2S_CTRL_LPBK_ENABLE (1 << 8) + +#define TEGRA30_I2S_BIT_CODE_LINEAR 0 +#define TEGRA30_I2S_BIT_CODE_ULAW 1 +#define TEGRA30_I2S_BIT_CODE_ALAW 2 + +#define TEGRA30_I2S_CTRL_BIT_CODE_SHIFT 4 +#define TEGRA30_I2S_CTRL_BIT_CODE_MASK (3 << TEGRA30_I2S_CTRL_BIT_CODE_SHIFT) +#define TEGRA30_I2S_CTRL_BIT_CODE_LINEAR (TEGRA30_I2S_BIT_CODE_LINEAR << TEGRA30_I2S_CTRL_BIT_CODE_SHIFT) +#define TEGRA30_I2S_CTRL_BIT_CODE_ULAW (TEGRA30_I2S_BIT_CODE_ULAW << TEGRA30_I2S_CTRL_BIT_CODE_SHIFT) +#define TEGRA30_I2S_CTRL_BIT_CODE_ALAW (TEGRA30_I2S_BIT_CODE_ALAW << TEGRA30_I2S_CTRL_BIT_CODE_SHIFT) + +#define TEGRA30_I2S_BITS_8 1 +#define TEGRA30_I2S_BITS_12 2 +#define TEGRA30_I2S_BITS_16 3 +#define TEGRA30_I2S_BITS_20 4 +#define TEGRA30_I2S_BITS_24 5 +#define TEGRA30_I2S_BITS_28 6 +#define TEGRA30_I2S_BITS_32 7 + +/* Sample container size; see {RX,TX}_MASK field in CH_CTRL below */ +#define TEGRA30_I2S_CTRL_BIT_SIZE_SHIFT 0 +#define TEGRA30_I2S_CTRL_BIT_SIZE_MASK (7 << TEGRA30_I2S_CTRL_BIT_SIZE_SHIFT) +#define TEGRA30_I2S_CTRL_BIT_SIZE_8 (TEGRA30_I2S_BITS_8 << TEGRA30_I2S_CTRL_BIT_SIZE_SHIFT) +#define TEGRA30_I2S_CTRL_BIT_SIZE_12 (TEGRA30_I2S_BITS_12 << TEGRA30_I2S_CTRL_BIT_SIZE_SHIFT) +#define TEGRA30_I2S_CTRL_BIT_SIZE_16 (TEGRA30_I2S_BITS_16 << TEGRA30_I2S_CTRL_BIT_SIZE_SHIFT) +#define TEGRA30_I2S_CTRL_BIT_SIZE_20 (TEGRA30_I2S_BITS_20 << TEGRA30_I2S_CTRL_BIT_SIZE_SHIFT) +#define TEGRA30_I2S_CTRL_BIT_SIZE_24 (TEGRA30_I2S_BITS_24 << TEGRA30_I2S_CTRL_BIT_SIZE_SHIFT) +#define TEGRA30_I2S_CTRL_BIT_SIZE_28 (TEGRA30_I2S_BITS_28 << TEGRA30_I2S_CTRL_BIT_SIZE_SHIFT) +#define TEGRA30_I2S_CTRL_BIT_SIZE_32 (TEGRA30_I2S_BITS_32 << TEGRA30_I2S_CTRL_BIT_SIZE_SHIFT) + +/* Fields in TEGRA30_I2S_TIMING */ + +#define TEGRA30_I2S_TIMING_NON_SYM_ENABLE (1 << 12) +#define TEGRA30_I2S_TIMING_CHANNEL_BIT_COUNT_SHIFT 0 +#define TEGRA30_I2S_TIMING_CHANNEL_BIT_COUNT_MASK_US 0x7fff +#define TEGRA30_I2S_TIMING_CHANNEL_BIT_COUNT_MASK (TEGRA30_I2S_TIMING_CHANNEL_BIT_COUNT_MASK_US << TEGRA30_I2S_TIMING_CHANNEL_BIT_COUNT_SHIFT) + +/* Fields in TEGRA30_I2S_OFFSET */ + +#define TEGRA30_I2S_OFFSET_RX_DATA_OFFSET_SHIFT 16 +#define TEGRA30_I2S_OFFSET_RX_DATA_OFFSET_MASK_US 0x7ff +#define TEGRA30_I2S_OFFSET_RX_DATA_OFFSET_MASK (TEGRA30_I2S_OFFSET_RX_DATA_OFFSET_MASK_US << TEGRA30_I2S_OFFSET_RX_DATA_OFFSET_SHIFT) +#define TEGRA30_I2S_OFFSET_TX_DATA_OFFSET_SHIFT 0 +#define TEGRA30_I2S_OFFSET_TX_DATA_OFFSET_MASK_US 0x7ff +#define TEGRA30_I2S_OFFSET_TX_DATA_OFFSET_MASK (TEGRA30_I2S_OFFSET_TX_DATA_OFFSET_MASK_US << TEGRA30_I2S_OFFSET_TX_DATA_OFFSET_SHIFT) + +/* Fields in TEGRA30_I2S_CH_CTRL */ + +/* (FSYNC width - 1) in bit clocks */ +#define TEGRA30_I2S_CH_CTRL_FSYNC_WIDTH_SHIFT 24 +#define TEGRA30_I2S_CH_CTRL_FSYNC_WIDTH_MASK_US 0xff +#define TEGRA30_I2S_CH_CTRL_FSYNC_WIDTH_MASK (TEGRA30_I2S_CH_CTRL_FSYNC_WIDTH_MASK_US << TEGRA30_I2S_CH_CTRL_FSYNC_WIDTH_SHIFT) + +#define TEGRA30_I2S_HIGHZ_NO 0 +#define TEGRA30_I2S_HIGHZ_YES 1 +#define TEGRA30_I2S_HIGHZ_ON_HALF_BIT_CLK 2 + +#define TEGRA30_I2S_CH_CTRL_HIGHZ_CTRL_SHIFT 12 +#define TEGRA30_I2S_CH_CTRL_HIGHZ_CTRL_MASK (3 << TEGRA30_I2S_CH_CTRL_HIGHZ_CTRL_SHIFT) +#define TEGRA30_I2S_CH_CTRL_HIGHZ_CTRL_NO (TEGRA30_I2S_HIGHZ_NO << TEGRA30_I2S_CH_CTRL_HIGHZ_CTRL_SHIFT) +#define TEGRA30_I2S_CH_CTRL_HIGHZ_CTRL_YES (TEGRA30_I2S_HIGHZ_YES << TEGRA30_I2S_CH_CTRL_HIGHZ_CTRL_SHIFT) +#define TEGRA30_I2S_CH_CTRL_HIGHZ_CTRL_ON_HALF_BIT_CLK (TEGRA30_I2S_HIGHZ_ON_HALF_BIT_CLK << TEGRA30_I2S_CH_CTRL_HIGHZ_CTRL_SHIFT) + +#define TEGRA30_I2S_MSB_FIRST 0 +#define TEGRA30_I2S_LSB_FIRST 1 + +#define TEGRA30_I2S_CH_CTRL_RX_BIT_ORDER_SHIFT 10 +#define TEGRA30_I2S_CH_CTRL_RX_BIT_ORDER_MASK (1 << TEGRA30_I2S_CH_CTRL_RX_BIT_ORDER_SHIFT) +#define TEGRA30_I2S_CH_CTRL_RX_BIT_ORDER_MSB_FIRST (TEGRA30_I2S_MSB_FIRST << TEGRA30_I2S_CH_CTRL_RX_BIT_ORDER_SHIFT) +#define TEGRA30_I2S_CH_CTRL_RX_BIT_ORDER_LSB_FIRST (TEGRA30_I2S_LSB_FIRST << TEGRA30_I2S_CH_CTRL_RX_BIT_ORDER_SHIFT) +#define TEGRA30_I2S_CH_CTRL_TX_BIT_ORDER_SHIFT 9 +#define TEGRA30_I2S_CH_CTRL_TX_BIT_ORDER_MASK (1 << TEGRA30_I2S_CH_CTRL_TX_BIT_ORDER_SHIFT) +#define TEGRA30_I2S_CH_CTRL_TX_BIT_ORDER_MSB_FIRST (TEGRA30_I2S_MSB_FIRST << TEGRA30_I2S_CH_CTRL_TX_BIT_ORDER_SHIFT) +#define TEGRA30_I2S_CH_CTRL_TX_BIT_ORDER_LSB_FIRST (TEGRA30_I2S_LSB_FIRST << TEGRA30_I2S_CH_CTRL_TX_BIT_ORDER_SHIFT) + +#define TEGRA30_I2S_POS_EDGE 0 +#define TEGRA30_I2S_NEG_EDGE 1 + +#define TEGRA30_I2S_CH_CTRL_EGDE_CTRL_SHIFT 8 +#define TEGRA30_I2S_CH_CTRL_EGDE_CTRL_MASK (1 << TEGRA30_I2S_CH_CTRL_EGDE_CTRL_SHIFT) +#define TEGRA30_I2S_CH_CTRL_EGDE_CTRL_POS_EDGE (TEGRA30_I2S_POS_EDGE << TEGRA30_I2S_CH_CTRL_EGDE_CTRL_SHIFT) +#define TEGRA30_I2S_CH_CTRL_EGDE_CTRL_NEG_EDGE (TEGRA30_I2S_NEG_EDGE << TEGRA30_I2S_CH_CTRL_EGDE_CTRL_SHIFT) + +/* Sample size is # bits from BIT_SIZE minus this field */ +#define TEGRA30_I2S_CH_CTRL_RX_MASK_BITS_SHIFT 4 +#define TEGRA30_I2S_CH_CTRL_RX_MASK_BITS_MASK_US 7 +#define TEGRA30_I2S_CH_CTRL_RX_MASK_BITS_MASK (TEGRA30_I2S_CH_CTRL_RX_MASK_BITS_MASK_US << TEGRA30_I2S_CH_CTRL_RX_MASK_BITS_SHIFT) + +#define TEGRA30_I2S_CH_CTRL_TX_MASK_BITS_SHIFT 0 +#define TEGRA30_I2S_CH_CTRL_TX_MASK_BITS_MASK_US 7 +#define TEGRA30_I2S_CH_CTRL_TX_MASK_BITS_MASK (TEGRA30_I2S_CH_CTRL_TX_MASK_BITS_MASK_US << TEGRA30_I2S_CH_CTRL_TX_MASK_BITS_SHIFT) + +/* Fields in TEGRA30_I2S_SLOT_CTRL */ + +/* Number of slots in frame, minus 1 */ +#define TEGRA30_I2S_SLOT_CTRL_TOTAL_SLOTS_SHIFT 16 +#define TEGRA30_I2S_SLOT_CTRL_TOTAL_SLOTS_MASK_US 7 +#define TEGRA30_I2S_SLOT_CTRL_TOTAL_SLOTS_MASK (TEGRA30_I2S_SLOT_CTRL_TOTAL_SLOT_MASK_US << TEGRA30_I2S_SLOT_CTRL_TOTAL_SLOT_SHIFT) + +/* TDM mode slot enable bitmask */ +#define TEGRA30_I2S_SLOT_CTRL_RX_SLOT_ENABLES_SHIFT 8 +#define TEGRA30_I2S_SLOT_CTRL_RX_SLOT_ENABLES_MASK (0xff << TEGRA30_I2S_SLOT_CTRL_RX_SLOT_ENABLES_SHIFT) + +#define TEGRA30_I2S_SLOT_CTRL_TX_SLOT_ENABLES_SHIFT 0 +#define TEGRA30_I2S_SLOT_CTRL_TX_SLOT_ENABLES_MASK (0xff << TEGRA30_I2S_SLOT_CTRL_TX_SLOT_ENABLES_SHIFT) + +/* Fields in TEGRA30_I2S_CIF_RX_CTRL */ +/* Uses field from TEGRA30_AUDIOCIF_CTRL_* in tegra30_ahub.h */ + +/* Fields in TEGRA30_I2S_CIF_TX_CTRL */ +/* Uses field from TEGRA30_AUDIOCIF_CTRL_* in tegra30_ahub.h */ + +/* Fields in TEGRA30_I2S_FLOWCTL */ + +#define TEGRA30_I2S_FILTER_LINEAR 0 +#define TEGRA30_I2S_FILTER_QUAD 1 + +#define TEGRA30_I2S_FLOWCTL_FILTER_SHIFT 31 +#define TEGRA30_I2S_FLOWCTL_FILTER_MASK (1 << TEGRA30_I2S_FLOWCTL_FILTER_SHIFT) +#define TEGRA30_I2S_FLOWCTL_FILTER_LINEAR (TEGRA30_I2S_FILTER_LINEAR << TEGRA30_I2S_FLOWCTL_FILTER_SHIFT) +#define TEGRA30_I2S_FLOWCTL_FILTER_QUAD (TEGRA30_I2S_FILTER_QUAD << TEGRA30_I2S_FLOWCTL_FILTER_SHIFT) + +/* Fields in TEGRA30_I2S_TX_STEP */ + +#define TEGRA30_I2S_TX_STEP_SHIFT 0 +#define TEGRA30_I2S_TX_STEP_MASK_US 0xffff +#define TEGRA30_I2S_TX_STEP_MASK (TEGRA30_I2S_TX_STEP_MASK_US << TEGRA30_I2S_TX_STEP_SHIFT) + +/* Fields in TEGRA30_I2S_FLOW_STATUS */ + +#define TEGRA30_I2S_FLOW_STATUS_UNDERFLOW (1 << 31) +#define TEGRA30_I2S_FLOW_STATUS_OVERFLOW (1 << 30) +#define TEGRA30_I2S_FLOW_STATUS_MONITOR_INT_EN (1 << 4) +#define TEGRA30_I2S_FLOW_STATUS_COUNTER_CLR (1 << 3) +#define TEGRA30_I2S_FLOW_STATUS_MONITOR_CLR (1 << 2) +#define TEGRA30_I2S_FLOW_STATUS_COUNTER_EN (1 << 1) +#define TEGRA30_I2S_FLOW_STATUS_MONITOR_EN (1 << 0) + +/* + * There are no fields in TEGRA30_I2S_FLOW_TOTAL, TEGRA30_I2S_FLOW_OVER, + * TEGRA30_I2S_FLOW_UNDER; they are counters taking the whole register. + */ + +/* Fields in TEGRA30_I2S_LCOEF_* */ + +#define TEGRA30_I2S_LCOEF_COEF_SHIFT 0 +#define TEGRA30_I2S_LCOEF_COEF_MASK_US 0xffff +#define TEGRA30_I2S_LCOEF_COEF_MASK (TEGRA30_I2S_LCOEF_COEF_MASK_US << TEGRA30_I2S_LCOEF_COEF_SHIFT) + +struct tegra30_i2s { + struct snd_soc_dai_driver dai; + int cif_id; + struct clk *clk_i2s; + enum tegra30_ahub_txcif capture_i2s_cif; + enum tegra30_ahub_rxcif capture_fifo_cif; + struct tegra_pcm_dma_params capture_dma_data; + enum tegra30_ahub_rxcif playback_i2s_cif; + enum tegra30_ahub_txcif playback_fifo_cif; + struct tegra_pcm_dma_params playback_dma_data; + struct regmap *regmap; + u32 reg_ctrl; +}; + +#endif diff --git a/sound/soc/tegra/tegra_alc5632.c b/sound/soc/tegra/tegra_alc5632.c index e45ccd851f6..32de7006daf 100644 --- a/sound/soc/tegra/tegra_alc5632.c +++ b/sound/soc/tegra/tegra_alc5632.c @@ -1,16 +1,17 @@ /* -* tegra_alc5632.c -- Toshiba AC100(PAZ00) machine ASoC driver -* -* Copyright (C) 2011 The AC100 Kernel Team <ac100@lists.lauchpad.net> -* -* Authors: Leon Romanovsky <leon@leon.nu> -* Andrey Danin <danindrey@mail.ru> -* Marc Dietrich <marvin24@gmx.de> -* -* 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. -*/ + * tegra_alc5632.c -- Toshiba AC100(PAZ00) machine ASoC driver + * + * Copyright (C) 2011 The AC100 Kernel Team <ac100@lists.lauchpad.net> + * Copyright (C) 2012 - NVIDIA, Inc. + * + * Authors: Leon Romanovsky <leon@leon.nu> + * Andrey Danin <danindrey@mail.ru> + * Marc Dietrich <marvin24@gmx.de> + * + * 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 <asm/mach-types.h> @@ -28,9 +29,6 @@ #include "../codecs/alc5632.h" -#include "tegra_das.h" -#include "tegra_i2s.h" -#include "tegra_pcm.h" #include "tegra_asoc_utils.h" #define DRV_NAME "tegra-alc5632" @@ -39,7 +37,6 @@ struct tegra_alc5632 { struct tegra_asoc_utils_data util_data; - struct platform_device *pcm_dev; int gpio_requested; int gpio_hp_det; }; @@ -140,7 +137,6 @@ static int tegra_alc5632_asoc_init(struct snd_soc_pcm_runtime *rtd) static struct snd_soc_dai_link tegra_alc5632_dai = { .name = "ALC5632", .stream_name = "ALC5632 PCM", - .platform_name = "tegra-pcm-audio", .codec_dai_name = "alc5632-hifi", .init = tegra_alc5632_asoc_init, .ops = &tegra_alc5632_asoc_ops, @@ -179,8 +175,6 @@ static __devinit int tegra_alc5632_probe(struct platform_device *pdev) platform_set_drvdata(pdev, card); snd_soc_card_set_drvdata(card, alc5632); - alc5632->pcm_dev = ERR_PTR(-EINVAL); - if (!(pdev->dev.of_node)) { dev_err(&pdev->dev, "Must be instantiated using device tree\n"); ret = -EINVAL; @@ -214,18 +208,11 @@ static __devinit int tegra_alc5632_probe(struct platform_device *pdev) goto err; } - alc5632->pcm_dev = platform_device_register_simple( - "tegra-pcm-audio", -1, NULL, 0); - if (IS_ERR(alc5632->pcm_dev)) { - dev_err(&pdev->dev, - "Can't instantiate tegra-pcm-audio\n"); - ret = PTR_ERR(alc5632->pcm_dev); - goto err; - } + tegra_alc5632_dai.platform_of_node = tegra_alc5632_dai.cpu_dai_of_node; ret = tegra_asoc_utils_init(&alc5632->util_data, &pdev->dev); if (ret) - goto err_unregister; + goto err; ret = snd_soc_register_card(card); if (ret) { @@ -238,9 +225,6 @@ static __devinit int tegra_alc5632_probe(struct platform_device *pdev) err_fini_utils: tegra_asoc_utils_fini(&alc5632->util_data); -err_unregister: - if (!IS_ERR(alc5632->pcm_dev)) - platform_device_unregister(alc5632->pcm_dev); err: return ret; } @@ -259,8 +243,6 @@ static int __devexit tegra_alc5632_remove(struct platform_device *pdev) snd_soc_unregister_card(card); tegra_asoc_utils_fini(&machine->util_data); - if (!IS_ERR(machine->pcm_dev)) - platform_device_unregister(machine->pcm_dev); return 0; } diff --git a/sound/soc/tegra/tegra_asoc_utils.c b/sound/soc/tegra/tegra_asoc_utils.c index f8428e410e0..9515ce58ea0 100644 --- a/sound/soc/tegra/tegra_asoc_utils.c +++ b/sound/soc/tegra/tegra_asoc_utils.c @@ -2,7 +2,7 @@ * tegra_asoc_utils.c - Harmony machine ASoC driver * * Author: Stephen Warren <swarren@nvidia.com> - * Copyright (C) 2010 - NVIDIA, Inc. + * Copyright (C) 2010,2012 - NVIDIA, Inc. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -25,6 +25,7 @@ #include <linux/err.h> #include <linux/kernel.h> #include <linux/module.h> +#include <linux/of.h> #include "tegra_asoc_utils.h" @@ -40,7 +41,10 @@ int tegra_asoc_utils_set_rate(struct tegra_asoc_utils_data *data, int srate, case 22050: case 44100: case 88200: - new_baseclock = 56448000; + if (data->soc == TEGRA_ASOC_UTILS_SOC_TEGRA20) + new_baseclock = 56448000; + else + new_baseclock = 564480000; break; case 8000: case 16000: @@ -48,7 +52,10 @@ int tegra_asoc_utils_set_rate(struct tegra_asoc_utils_data *data, int srate, case 48000: case 64000: case 96000: - new_baseclock = 73728000; + if (data->soc == TEGRA_ASOC_UTILS_SOC_TEGRA20) + new_baseclock = 73728000; + else + new_baseclock = 552960000; break; default: return -EINVAL; @@ -78,7 +85,7 @@ int tegra_asoc_utils_set_rate(struct tegra_asoc_utils_data *data, int srate, return err; } - /* Don't set cdev1 rate; its locked to pll_a_out0 */ + /* Don't set cdev1/extern1 rate; it's locked to pll_a_out0 */ err = clk_enable(data->clk_pll_a); if (err) { @@ -112,6 +119,17 @@ int tegra_asoc_utils_init(struct tegra_asoc_utils_data *data, data->dev = dev; + if (of_machine_is_compatible("nvidia,tegra20")) + data->soc = TEGRA_ASOC_UTILS_SOC_TEGRA20; + else if (of_machine_is_compatible("nvidia,tegra30")) + data->soc = TEGRA_ASOC_UTILS_SOC_TEGRA30; + else if (!dev->of_node) + /* non-DT is always Tegra20 */ + data->soc = TEGRA_ASOC_UTILS_SOC_TEGRA20; + else + /* DT boot, but unknown SoC */ + return -EINVAL; + data->clk_pll_a = clk_get_sys(NULL, "pll_a"); if (IS_ERR(data->clk_pll_a)) { dev_err(data->dev, "Can't retrieve clk pll_a\n"); @@ -126,15 +144,24 @@ int tegra_asoc_utils_init(struct tegra_asoc_utils_data *data, goto err_put_pll_a; } - data->clk_cdev1 = clk_get_sys(NULL, "cdev1"); + if (data->soc == TEGRA_ASOC_UTILS_SOC_TEGRA20) + data->clk_cdev1 = clk_get_sys(NULL, "cdev1"); + else + data->clk_cdev1 = clk_get_sys("extern1", NULL); if (IS_ERR(data->clk_cdev1)) { dev_err(data->dev, "Can't retrieve clk cdev1\n"); ret = PTR_ERR(data->clk_cdev1); goto err_put_pll_a_out0; } + ret = tegra_asoc_utils_set_rate(data, 44100, 256 * 44100); + if (ret) + goto err_put_cdev1; + return 0; +err_put_cdev1: + clk_put(data->clk_cdev1); err_put_pll_a_out0: clk_put(data->clk_pll_a_out0); err_put_pll_a: diff --git a/sound/soc/tegra/tegra_asoc_utils.h b/sound/soc/tegra/tegra_asoc_utils.h index 4818195da25..44db1dbb8f2 100644 --- a/sound/soc/tegra/tegra_asoc_utils.h +++ b/sound/soc/tegra/tegra_asoc_utils.h @@ -2,7 +2,7 @@ * tegra_asoc_utils.h - Definitions for Tegra DAS driver * * Author: Stephen Warren <swarren@nvidia.com> - * Copyright (C) 2010 - NVIDIA, Inc. + * Copyright (C) 2010,2012 - NVIDIA, Inc. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -26,8 +26,14 @@ struct clk; struct device; +enum tegra_asoc_utils_soc { + TEGRA_ASOC_UTILS_SOC_TEGRA20, + TEGRA_ASOC_UTILS_SOC_TEGRA30, +}; + struct tegra_asoc_utils_data { struct device *dev; + enum tegra_asoc_utils_soc soc; struct clk *clk_pll_a; struct clk *clk_pll_a_out0; struct clk *clk_cdev1; @@ -42,4 +48,3 @@ int tegra_asoc_utils_init(struct tegra_asoc_utils_data *data, void tegra_asoc_utils_fini(struct tegra_asoc_utils_data *data); #endif - diff --git a/sound/soc/tegra/tegra_das.c b/sound/soc/tegra/tegra_das.c deleted file mode 100644 index 3b3c1ba4d23..00000000000 --- a/sound/soc/tegra/tegra_das.c +++ /dev/null @@ -1,261 +0,0 @@ -/* - * tegra_das.c - Tegra DAS driver - * - * Author: Stephen Warren <swarren@nvidia.com> - * Copyright (C) 2010 - NVIDIA, Inc. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * version 2 as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * 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/debugfs.h> -#include <linux/device.h> -#include <linux/platform_device.h> -#include <linux/seq_file.h> -#include <linux/slab.h> -#include <linux/io.h> -#include <mach/iomap.h> -#include <sound/soc.h> -#include "tegra_das.h" - -#define DRV_NAME "tegra-das" - -static struct tegra_das *das; - -static inline void tegra_das_write(u32 reg, u32 val) -{ - __raw_writel(val, das->regs + reg); -} - -static inline u32 tegra_das_read(u32 reg) -{ - return __raw_readl(das->regs + reg); -} - -int tegra_das_connect_dap_to_dac(int dap, int dac) -{ - u32 addr; - u32 reg; - - if (!das) - return -ENODEV; - - addr = TEGRA_DAS_DAP_CTRL_SEL + - (dap * TEGRA_DAS_DAP_CTRL_SEL_STRIDE); - reg = dac << TEGRA_DAS_DAP_CTRL_SEL_DAP_CTRL_SEL_P; - - tegra_das_write(addr, reg); - - return 0; -} -EXPORT_SYMBOL_GPL(tegra_das_connect_dap_to_dac); - -int tegra_das_connect_dap_to_dap(int dap, int otherdap, int master, - int sdata1rx, int sdata2rx) -{ - u32 addr; - u32 reg; - - if (!das) - return -ENODEV; - - addr = TEGRA_DAS_DAP_CTRL_SEL + - (dap * TEGRA_DAS_DAP_CTRL_SEL_STRIDE); - reg = otherdap << TEGRA_DAS_DAP_CTRL_SEL_DAP_CTRL_SEL_P | - !!sdata2rx << TEGRA_DAS_DAP_CTRL_SEL_DAP_SDATA2_TX_RX_P | - !!sdata1rx << TEGRA_DAS_DAP_CTRL_SEL_DAP_SDATA1_TX_RX_P | - !!master << TEGRA_DAS_DAP_CTRL_SEL_DAP_MS_SEL_P; - - tegra_das_write(addr, reg); - - return 0; -} -EXPORT_SYMBOL_GPL(tegra_das_connect_dap_to_dap); - -int tegra_das_connect_dac_to_dap(int dac, int dap) -{ - u32 addr; - u32 reg; - - if (!das) - return -ENODEV; - - addr = TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL + - (dac * TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_STRIDE); - reg = dap << TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_CLK_SEL_P | - dap << TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA1_SEL_P | - dap << TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA2_SEL_P; - - tegra_das_write(addr, reg); - - return 0; -} -EXPORT_SYMBOL_GPL(tegra_das_connect_dac_to_dap); - -#ifdef CONFIG_DEBUG_FS -static int tegra_das_show(struct seq_file *s, void *unused) -{ - int i; - u32 addr; - u32 reg; - - for (i = 0; i < TEGRA_DAS_DAP_CTRL_SEL_COUNT; i++) { - addr = TEGRA_DAS_DAP_CTRL_SEL + - (i * TEGRA_DAS_DAP_CTRL_SEL_STRIDE); - reg = tegra_das_read(addr); - seq_printf(s, "TEGRA_DAS_DAP_CTRL_SEL[%d] = %08x\n", i, reg); - } - - for (i = 0; i < TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_COUNT; i++) { - addr = TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL + - (i * TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_STRIDE); - reg = tegra_das_read(addr); - seq_printf(s, "TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL[%d] = %08x\n", - i, reg); - } - - return 0; -} - -static int tegra_das_debug_open(struct inode *inode, struct file *file) -{ - return single_open(file, tegra_das_show, inode->i_private); -} - -static const struct file_operations tegra_das_debug_fops = { - .open = tegra_das_debug_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, -}; - -static void tegra_das_debug_add(struct tegra_das *das) -{ - das->debug = debugfs_create_file(DRV_NAME, S_IRUGO, - snd_soc_debugfs_root, das, - &tegra_das_debug_fops); -} - -static void tegra_das_debug_remove(struct tegra_das *das) -{ - if (das->debug) - debugfs_remove(das->debug); -} -#else -static inline void tegra_das_debug_add(struct tegra_das *das) -{ -} - -static inline void tegra_das_debug_remove(struct tegra_das *das) -{ -} -#endif - -static int __devinit tegra_das_probe(struct platform_device *pdev) -{ - struct resource *res, *region; - int ret = 0; - - if (das) - return -ENODEV; - - das = devm_kzalloc(&pdev->dev, sizeof(struct tegra_das), GFP_KERNEL); - if (!das) { - dev_err(&pdev->dev, "Can't allocate tegra_das\n"); - ret = -ENOMEM; - goto err; - } - das->dev = &pdev->dev; - - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!res) { - dev_err(&pdev->dev, "No memory resource\n"); - ret = -ENODEV; - goto err; - } - - region = devm_request_mem_region(&pdev->dev, res->start, - resource_size(res), pdev->name); - if (!region) { - dev_err(&pdev->dev, "Memory region already claimed\n"); - ret = -EBUSY; - goto err; - } - - das->regs = devm_ioremap(&pdev->dev, res->start, resource_size(res)); - if (!das->regs) { - dev_err(&pdev->dev, "ioremap failed\n"); - ret = -ENOMEM; - goto err; - } - - ret = tegra_das_connect_dap_to_dac(TEGRA_DAS_DAP_ID_1, - TEGRA_DAS_DAP_SEL_DAC1); - if (ret) { - dev_err(&pdev->dev, "Can't set up DAS DAP connection\n"); - goto err; - } - ret = tegra_das_connect_dac_to_dap(TEGRA_DAS_DAC_ID_1, - TEGRA_DAS_DAC_SEL_DAP1); - if (ret) { - dev_err(&pdev->dev, "Can't set up DAS DAC connection\n"); - goto err; - } - - tegra_das_debug_add(das); - - platform_set_drvdata(pdev, das); - - return 0; - -err: - das = NULL; - return ret; -} - -static int __devexit tegra_das_remove(struct platform_device *pdev) -{ - if (!das) - return -ENODEV; - - tegra_das_debug_remove(das); - - das = NULL; - - return 0; -} - -static const struct of_device_id tegra_das_of_match[] __devinitconst = { - { .compatible = "nvidia,tegra20-das", }, - {}, -}; - -static struct platform_driver tegra_das_driver = { - .probe = tegra_das_probe, - .remove = __devexit_p(tegra_das_remove), - .driver = { - .name = DRV_NAME, - .owner = THIS_MODULE, - .of_match_table = tegra_das_of_match, - }, -}; -module_platform_driver(tegra_das_driver); - -MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>"); -MODULE_DESCRIPTION("Tegra DAS driver"); -MODULE_LICENSE("GPL"); -MODULE_ALIAS("platform:" DRV_NAME); -MODULE_DEVICE_TABLE(of, tegra_das_of_match); diff --git a/sound/soc/tegra/tegra_das.h b/sound/soc/tegra/tegra_das.h deleted file mode 100644 index 2c96c7b3c45..00000000000 --- a/sound/soc/tegra/tegra_das.h +++ /dev/null @@ -1,135 +0,0 @@ -/* - * tegra_das.h - Definitions for Tegra DAS driver - * - * Author: Stephen Warren <swarren@nvidia.com> - * Copyright (C) 2010 - NVIDIA, Inc. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * version 2 as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA - * 02110-1301 USA - * - */ - -#ifndef __TEGRA_DAS_H__ -#define __TEGRA_DAS_H__ - -/* Register TEGRA_DAS_DAP_CTRL_SEL */ -#define TEGRA_DAS_DAP_CTRL_SEL 0x00 -#define TEGRA_DAS_DAP_CTRL_SEL_COUNT 5 -#define TEGRA_DAS_DAP_CTRL_SEL_STRIDE 4 -#define TEGRA_DAS_DAP_CTRL_SEL_DAP_MS_SEL_P 31 -#define TEGRA_DAS_DAP_CTRL_SEL_DAP_MS_SEL_S 1 -#define TEGRA_DAS_DAP_CTRL_SEL_DAP_SDATA1_TX_RX_P 30 -#define TEGRA_DAS_DAP_CTRL_SEL_DAP_SDATA1_TX_RX_S 1 -#define TEGRA_DAS_DAP_CTRL_SEL_DAP_SDATA2_TX_RX_P 29 -#define TEGRA_DAS_DAP_CTRL_SEL_DAP_SDATA2_TX_RX_S 1 -#define TEGRA_DAS_DAP_CTRL_SEL_DAP_CTRL_SEL_P 0 -#define TEGRA_DAS_DAP_CTRL_SEL_DAP_CTRL_SEL_S 5 - -/* Values for field TEGRA_DAS_DAP_CTRL_SEL_DAP_CTRL_SEL */ -#define TEGRA_DAS_DAP_SEL_DAC1 0 -#define TEGRA_DAS_DAP_SEL_DAC2 1 -#define TEGRA_DAS_DAP_SEL_DAC3 2 -#define TEGRA_DAS_DAP_SEL_DAP1 16 -#define TEGRA_DAS_DAP_SEL_DAP2 17 -#define TEGRA_DAS_DAP_SEL_DAP3 18 -#define TEGRA_DAS_DAP_SEL_DAP4 19 -#define TEGRA_DAS_DAP_SEL_DAP5 20 - -/* Register TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL */ -#define TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL 0x40 -#define TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_COUNT 3 -#define TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_STRIDE 4 -#define TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA2_SEL_P 28 -#define TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA2_SEL_S 4 -#define TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA1_SEL_P 24 -#define TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA1_SEL_S 4 -#define TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_CLK_SEL_P 0 -#define TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_CLK_SEL_S 4 - -/* - * Values for: - * TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA2_SEL - * TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA1_SEL - * TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_CLK_SEL - */ -#define TEGRA_DAS_DAC_SEL_DAP1 0 -#define TEGRA_DAS_DAC_SEL_DAP2 1 -#define TEGRA_DAS_DAC_SEL_DAP3 2 -#define TEGRA_DAS_DAC_SEL_DAP4 3 -#define TEGRA_DAS_DAC_SEL_DAP5 4 - -/* - * Names/IDs of the DACs/DAPs. - */ - -#define TEGRA_DAS_DAP_ID_1 0 -#define TEGRA_DAS_DAP_ID_2 1 -#define TEGRA_DAS_DAP_ID_3 2 -#define TEGRA_DAS_DAP_ID_4 3 -#define TEGRA_DAS_DAP_ID_5 4 - -#define TEGRA_DAS_DAC_ID_1 0 -#define TEGRA_DAS_DAC_ID_2 1 -#define TEGRA_DAS_DAC_ID_3 2 - -struct tegra_das { - struct device *dev; - void __iomem *regs; - struct dentry *debug; -}; - -/* - * Terminology: - * DAS: Digital audio switch (HW module controlled by this driver) - * DAP: Digital audio port (port/pins on Tegra device) - * DAC: Digital audio controller (e.g. I2S or AC97 controller elsewhere) - * - * The Tegra DAS is a mux/cross-bar which can connect each DAP to a specific - * DAC, or another DAP. When DAPs are connected, one must be the master and - * one the slave. Each DAC allows selection of a specific DAP for input, to - * cater for the case where N DAPs are connected to 1 DAC for broadcast - * output. - * - * This driver is dumb; no attempt is made to ensure that a valid routing - * configuration is programmed. - */ - -/* - * Connect a DAP to to a DAC - * dap_id: DAP to connect: TEGRA_DAS_DAP_ID_* - * dac_sel: DAC to connect to: TEGRA_DAS_DAP_SEL_DAC* - */ -extern int tegra_das_connect_dap_to_dac(int dap_id, int dac_sel); - -/* - * Connect a DAP to to another DAP - * dap_id: DAP to connect: TEGRA_DAS_DAP_ID_* - * other_dap_sel: DAP to connect to: TEGRA_DAS_DAP_SEL_DAP* - * master: Is this DAP the master (1) or slave (0) - * sdata1rx: Is this DAP's SDATA1 pin RX (1) or TX (0) - * sdata2rx: Is this DAP's SDATA2 pin RX (1) or TX (0) - */ -extern int tegra_das_connect_dap_to_dap(int dap_id, int other_dap_sel, - int master, int sdata1rx, - int sdata2rx); - -/* - * Connect a DAC's input to a DAP - * (DAC outputs are selected by the DAP) - * dac_id: DAC ID to connect: TEGRA_DAS_DAC_ID_* - * dap_sel: DAP to receive input from: TEGRA_DAS_DAC_SEL_DAP* - */ -extern int tegra_das_connect_dac_to_dap(int dac_id, int dap_sel); - -#endif diff --git a/sound/soc/tegra/tegra_i2s.c b/sound/soc/tegra/tegra_i2s.c deleted file mode 100644 index e53349912b2..00000000000 --- a/sound/soc/tegra/tegra_i2s.c +++ /dev/null @@ -1,459 +0,0 @@ -/* - * tegra_i2s.c - Tegra I2S driver - * - * Author: Stephen Warren <swarren@nvidia.com> - * Copyright (C) 2010 - NVIDIA, Inc. - * - * Based on code copyright/by: - * - * Copyright (c) 2009-2010, NVIDIA Corporation. - * Scott Peterson <speterson@nvidia.com> - * - * Copyright (C) 2010 Google, Inc. - * Iliyan Malchev <malchev@google.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. - * - * 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/clk.h> -#include <linux/module.h> -#include <linux/debugfs.h> -#include <linux/device.h> -#include <linux/platform_device.h> -#include <linux/seq_file.h> -#include <linux/slab.h> -#include <linux/io.h> -#include <linux/of.h> -#include <mach/iomap.h> -#include <sound/core.h> -#include <sound/pcm.h> -#include <sound/pcm_params.h> -#include <sound/soc.h> - -#include "tegra_i2s.h" - -#define DRV_NAME "tegra-i2s" - -static inline void tegra_i2s_write(struct tegra_i2s *i2s, u32 reg, u32 val) -{ - __raw_writel(val, i2s->regs + reg); -} - -static inline u32 tegra_i2s_read(struct tegra_i2s *i2s, u32 reg) -{ - return __raw_readl(i2s->regs + reg); -} - -#ifdef CONFIG_DEBUG_FS -static int tegra_i2s_show(struct seq_file *s, void *unused) -{ -#define REG(r) { r, #r } - static const struct { - int offset; - const char *name; - } regs[] = { - REG(TEGRA_I2S_CTRL), - REG(TEGRA_I2S_STATUS), - REG(TEGRA_I2S_TIMING), - REG(TEGRA_I2S_FIFO_SCR), - REG(TEGRA_I2S_PCM_CTRL), - REG(TEGRA_I2S_NW_CTRL), - REG(TEGRA_I2S_TDM_CTRL), - REG(TEGRA_I2S_TDM_TX_RX_CTRL), - }; -#undef REG - - struct tegra_i2s *i2s = s->private; - int i; - - clk_enable(i2s->clk_i2s); - - for (i = 0; i < ARRAY_SIZE(regs); i++) { - u32 val = tegra_i2s_read(i2s, regs[i].offset); - seq_printf(s, "%s = %08x\n", regs[i].name, val); - } - - clk_disable(i2s->clk_i2s); - - return 0; -} - -static int tegra_i2s_debug_open(struct inode *inode, struct file *file) -{ - return single_open(file, tegra_i2s_show, inode->i_private); -} - -static const struct file_operations tegra_i2s_debug_fops = { - .open = tegra_i2s_debug_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, -}; - -static void tegra_i2s_debug_add(struct tegra_i2s *i2s) -{ - i2s->debug = debugfs_create_file(i2s->dai.name, S_IRUGO, - snd_soc_debugfs_root, i2s, - &tegra_i2s_debug_fops); -} - -static void tegra_i2s_debug_remove(struct tegra_i2s *i2s) -{ - if (i2s->debug) - debugfs_remove(i2s->debug); -} -#else -static inline void tegra_i2s_debug_add(struct tegra_i2s *i2s) -{ -} - -static inline void tegra_i2s_debug_remove(struct tegra_i2s *i2s) -{ -} -#endif - -static int tegra_i2s_set_fmt(struct snd_soc_dai *dai, - unsigned int fmt) -{ - struct tegra_i2s *i2s = snd_soc_dai_get_drvdata(dai); - - switch (fmt & SND_SOC_DAIFMT_INV_MASK) { - case SND_SOC_DAIFMT_NB_NF: - break; - default: - return -EINVAL; - } - - i2s->reg_ctrl &= ~TEGRA_I2S_CTRL_MASTER_ENABLE; - switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { - case SND_SOC_DAIFMT_CBS_CFS: - i2s->reg_ctrl |= TEGRA_I2S_CTRL_MASTER_ENABLE; - break; - case SND_SOC_DAIFMT_CBM_CFM: - break; - default: - return -EINVAL; - } - - i2s->reg_ctrl &= ~(TEGRA_I2S_CTRL_BIT_FORMAT_MASK | - TEGRA_I2S_CTRL_LRCK_MASK); - switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { - case SND_SOC_DAIFMT_DSP_A: - i2s->reg_ctrl |= TEGRA_I2S_CTRL_BIT_FORMAT_DSP; - i2s->reg_ctrl |= TEGRA_I2S_CTRL_LRCK_L_LOW; - break; - case SND_SOC_DAIFMT_DSP_B: - i2s->reg_ctrl |= TEGRA_I2S_CTRL_BIT_FORMAT_DSP; - i2s->reg_ctrl |= TEGRA_I2S_CTRL_LRCK_R_LOW; - break; - case SND_SOC_DAIFMT_I2S: - i2s->reg_ctrl |= TEGRA_I2S_CTRL_BIT_FORMAT_I2S; - i2s->reg_ctrl |= TEGRA_I2S_CTRL_LRCK_L_LOW; - break; - case SND_SOC_DAIFMT_RIGHT_J: - i2s->reg_ctrl |= TEGRA_I2S_CTRL_BIT_FORMAT_RJM; - i2s->reg_ctrl |= TEGRA_I2S_CTRL_LRCK_L_LOW; - break; - case SND_SOC_DAIFMT_LEFT_J: - i2s->reg_ctrl |= TEGRA_I2S_CTRL_BIT_FORMAT_LJM; - i2s->reg_ctrl |= TEGRA_I2S_CTRL_LRCK_L_LOW; - break; - default: - return -EINVAL; - } - - return 0; -} - -static int tegra_i2s_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *params, - struct snd_soc_dai *dai) -{ - struct device *dev = substream->pcm->card->dev; - struct tegra_i2s *i2s = snd_soc_dai_get_drvdata(dai); - u32 reg; - int ret, sample_size, srate, i2sclock, bitcnt; - - i2s->reg_ctrl &= ~TEGRA_I2S_CTRL_BIT_SIZE_MASK; - switch (params_format(params)) { - case SNDRV_PCM_FORMAT_S16_LE: - i2s->reg_ctrl |= TEGRA_I2S_CTRL_BIT_SIZE_16; - sample_size = 16; - break; - case SNDRV_PCM_FORMAT_S24_LE: - i2s->reg_ctrl |= TEGRA_I2S_CTRL_BIT_SIZE_24; - sample_size = 24; - break; - case SNDRV_PCM_FORMAT_S32_LE: - i2s->reg_ctrl |= TEGRA_I2S_CTRL_BIT_SIZE_32; - sample_size = 32; - break; - default: - return -EINVAL; - } - - srate = params_rate(params); - - /* Final "* 2" required by Tegra hardware */ - i2sclock = srate * params_channels(params) * sample_size * 2; - - ret = clk_set_rate(i2s->clk_i2s, i2sclock); - if (ret) { - dev_err(dev, "Can't set I2S clock rate: %d\n", ret); - return ret; - } - - bitcnt = (i2sclock / (2 * srate)) - 1; - if (bitcnt < 0 || bitcnt > TEGRA_I2S_TIMING_CHANNEL_BIT_COUNT_MASK_US) - return -EINVAL; - reg = bitcnt << TEGRA_I2S_TIMING_CHANNEL_BIT_COUNT_SHIFT; - - if (i2sclock % (2 * srate)) - reg |= TEGRA_I2S_TIMING_NON_SYM_ENABLE; - - if (!i2s->clk_refs) - clk_enable(i2s->clk_i2s); - - tegra_i2s_write(i2s, TEGRA_I2S_TIMING, reg); - - tegra_i2s_write(i2s, TEGRA_I2S_FIFO_SCR, - TEGRA_I2S_FIFO_SCR_FIFO2_ATN_LVL_FOUR_SLOTS | - TEGRA_I2S_FIFO_SCR_FIFO1_ATN_LVL_FOUR_SLOTS); - - if (!i2s->clk_refs) - clk_disable(i2s->clk_i2s); - - return 0; -} - -static void tegra_i2s_start_playback(struct tegra_i2s *i2s) -{ - i2s->reg_ctrl |= TEGRA_I2S_CTRL_FIFO1_ENABLE; - tegra_i2s_write(i2s, TEGRA_I2S_CTRL, i2s->reg_ctrl); -} - -static void tegra_i2s_stop_playback(struct tegra_i2s *i2s) -{ - i2s->reg_ctrl &= ~TEGRA_I2S_CTRL_FIFO1_ENABLE; - tegra_i2s_write(i2s, TEGRA_I2S_CTRL, i2s->reg_ctrl); -} - -static void tegra_i2s_start_capture(struct tegra_i2s *i2s) -{ - i2s->reg_ctrl |= TEGRA_I2S_CTRL_FIFO2_ENABLE; - tegra_i2s_write(i2s, TEGRA_I2S_CTRL, i2s->reg_ctrl); -} - -static void tegra_i2s_stop_capture(struct tegra_i2s *i2s) -{ - i2s->reg_ctrl &= ~TEGRA_I2S_CTRL_FIFO2_ENABLE; - tegra_i2s_write(i2s, TEGRA_I2S_CTRL, i2s->reg_ctrl); -} - -static int tegra_i2s_trigger(struct snd_pcm_substream *substream, int cmd, - struct snd_soc_dai *dai) -{ - struct tegra_i2s *i2s = snd_soc_dai_get_drvdata(dai); - - switch (cmd) { - case SNDRV_PCM_TRIGGER_START: - case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: - case SNDRV_PCM_TRIGGER_RESUME: - if (!i2s->clk_refs) - clk_enable(i2s->clk_i2s); - i2s->clk_refs++; - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - tegra_i2s_start_playback(i2s); - else - tegra_i2s_start_capture(i2s); - break; - case SNDRV_PCM_TRIGGER_STOP: - case SNDRV_PCM_TRIGGER_PAUSE_PUSH: - case SNDRV_PCM_TRIGGER_SUSPEND: - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - tegra_i2s_stop_playback(i2s); - else - tegra_i2s_stop_capture(i2s); - i2s->clk_refs--; - if (!i2s->clk_refs) - clk_disable(i2s->clk_i2s); - break; - default: - return -EINVAL; - } - - return 0; -} - -static int tegra_i2s_probe(struct snd_soc_dai *dai) -{ - struct tegra_i2s * i2s = snd_soc_dai_get_drvdata(dai); - - dai->capture_dma_data = &i2s->capture_dma_data; - dai->playback_dma_data = &i2s->playback_dma_data; - - return 0; -} - -static const struct snd_soc_dai_ops tegra_i2s_dai_ops = { - .set_fmt = tegra_i2s_set_fmt, - .hw_params = tegra_i2s_hw_params, - .trigger = tegra_i2s_trigger, -}; - -static const struct snd_soc_dai_driver tegra_i2s_dai_template = { - .probe = tegra_i2s_probe, - .playback = { - .channels_min = 2, - .channels_max = 2, - .rates = SNDRV_PCM_RATE_8000_96000, - .formats = SNDRV_PCM_FMTBIT_S16_LE, - }, - .capture = { - .channels_min = 2, - .channels_max = 2, - .rates = SNDRV_PCM_RATE_8000_96000, - .formats = SNDRV_PCM_FMTBIT_S16_LE, - }, - .ops = &tegra_i2s_dai_ops, - .symmetric_rates = 1, -}; - -static __devinit int tegra_i2s_platform_probe(struct platform_device *pdev) -{ - struct tegra_i2s * i2s; - struct resource *mem, *memregion, *dmareq; - u32 of_dma[2]; - u32 dma_ch; - int ret; - - i2s = devm_kzalloc(&pdev->dev, sizeof(struct tegra_i2s), GFP_KERNEL); - if (!i2s) { - dev_err(&pdev->dev, "Can't allocate tegra_i2s\n"); - ret = -ENOMEM; - goto err; - } - dev_set_drvdata(&pdev->dev, i2s); - - i2s->dai = tegra_i2s_dai_template; - i2s->dai.name = dev_name(&pdev->dev); - - i2s->clk_i2s = clk_get(&pdev->dev, NULL); - if (IS_ERR(i2s->clk_i2s)) { - dev_err(&pdev->dev, "Can't retrieve i2s clock\n"); - ret = PTR_ERR(i2s->clk_i2s); - goto err; - } - - mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!mem) { - dev_err(&pdev->dev, "No memory resource\n"); - ret = -ENODEV; - goto err_clk_put; - } - - dmareq = platform_get_resource(pdev, IORESOURCE_DMA, 0); - if (!dmareq) { - if (of_property_read_u32_array(pdev->dev.of_node, - "nvidia,dma-request-selector", - of_dma, 2) < 0) { - dev_err(&pdev->dev, "No DMA resource\n"); - ret = -ENODEV; - goto err_clk_put; - } - dma_ch = of_dma[1]; - } else { - dma_ch = dmareq->start; - } - - memregion = devm_request_mem_region(&pdev->dev, mem->start, - resource_size(mem), DRV_NAME); - if (!memregion) { - dev_err(&pdev->dev, "Memory region already claimed\n"); - ret = -EBUSY; - goto err_clk_put; - } - - i2s->regs = devm_ioremap(&pdev->dev, mem->start, resource_size(mem)); - if (!i2s->regs) { - dev_err(&pdev->dev, "ioremap failed\n"); - ret = -ENOMEM; - goto err_clk_put; - } - - i2s->capture_dma_data.addr = mem->start + TEGRA_I2S_FIFO2; - i2s->capture_dma_data.wrap = 4; - i2s->capture_dma_data.width = 32; - i2s->capture_dma_data.req_sel = dma_ch; - - i2s->playback_dma_data.addr = mem->start + TEGRA_I2S_FIFO1; - i2s->playback_dma_data.wrap = 4; - i2s->playback_dma_data.width = 32; - i2s->playback_dma_data.req_sel = dma_ch; - - i2s->reg_ctrl = TEGRA_I2S_CTRL_FIFO_FORMAT_PACKED; - - ret = snd_soc_register_dai(&pdev->dev, &i2s->dai); - if (ret) { - dev_err(&pdev->dev, "Could not register DAI: %d\n", ret); - ret = -ENOMEM; - goto err_clk_put; - } - - tegra_i2s_debug_add(i2s); - - return 0; - -err_clk_put: - clk_put(i2s->clk_i2s); -err: - return ret; -} - -static int __devexit tegra_i2s_platform_remove(struct platform_device *pdev) -{ - struct tegra_i2s *i2s = dev_get_drvdata(&pdev->dev); - - snd_soc_unregister_dai(&pdev->dev); - - tegra_i2s_debug_remove(i2s); - - clk_put(i2s->clk_i2s); - - return 0; -} - -static const struct of_device_id tegra_i2s_of_match[] __devinitconst = { - { .compatible = "nvidia,tegra20-i2s", }, - {}, -}; - -static struct platform_driver tegra_i2s_driver = { - .driver = { - .name = DRV_NAME, - .owner = THIS_MODULE, - .of_match_table = tegra_i2s_of_match, - }, - .probe = tegra_i2s_platform_probe, - .remove = __devexit_p(tegra_i2s_platform_remove), -}; -module_platform_driver(tegra_i2s_driver); - -MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>"); -MODULE_DESCRIPTION("Tegra I2S ASoC driver"); -MODULE_LICENSE("GPL"); -MODULE_ALIAS("platform:" DRV_NAME); -MODULE_DEVICE_TABLE(of, tegra_i2s_of_match); diff --git a/sound/soc/tegra/tegra_i2s.h b/sound/soc/tegra/tegra_i2s.h deleted file mode 100644 index 15ce1e2e8bd..00000000000 --- a/sound/soc/tegra/tegra_i2s.h +++ /dev/null @@ -1,166 +0,0 @@ -/* - * tegra_i2s.h - Definitions for Tegra I2S driver - * - * Author: Stephen Warren <swarren@nvidia.com> - * Copyright (C) 2010 - NVIDIA, Inc. - * - * Based on code copyright/by: - * - * Copyright (c) 2009-2010, NVIDIA Corporation. - * Scott Peterson <speterson@nvidia.com> - * - * Copyright (C) 2010 Google, Inc. - * Iliyan Malchev <malchev@google.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. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA - * 02110-1301 USA - * - */ - -#ifndef __TEGRA_I2S_H__ -#define __TEGRA_I2S_H__ - -#include "tegra_pcm.h" - -/* Register offsets from TEGRA_I2S1_BASE and TEGRA_I2S2_BASE */ - -#define TEGRA_I2S_CTRL 0x00 -#define TEGRA_I2S_STATUS 0x04 -#define TEGRA_I2S_TIMING 0x08 -#define TEGRA_I2S_FIFO_SCR 0x0c -#define TEGRA_I2S_PCM_CTRL 0x10 -#define TEGRA_I2S_NW_CTRL 0x14 -#define TEGRA_I2S_TDM_CTRL 0x20 -#define TEGRA_I2S_TDM_TX_RX_CTRL 0x24 -#define TEGRA_I2S_FIFO1 0x40 -#define TEGRA_I2S_FIFO2 0x80 - -/* Fields in TEGRA_I2S_CTRL */ - -#define TEGRA_I2S_CTRL_FIFO2_TX_ENABLE (1 << 30) -#define TEGRA_I2S_CTRL_FIFO1_ENABLE (1 << 29) -#define TEGRA_I2S_CTRL_FIFO2_ENABLE (1 << 28) -#define TEGRA_I2S_CTRL_FIFO1_RX_ENABLE (1 << 27) -#define TEGRA_I2S_CTRL_FIFO_LPBK_ENABLE (1 << 26) -#define TEGRA_I2S_CTRL_MASTER_ENABLE (1 << 25) - -#define TEGRA_I2S_LRCK_LEFT_LOW 0 -#define TEGRA_I2S_LRCK_RIGHT_LOW 1 - -#define TEGRA_I2S_CTRL_LRCK_SHIFT 24 -#define TEGRA_I2S_CTRL_LRCK_MASK (1 << TEGRA_I2S_CTRL_LRCK_SHIFT) -#define TEGRA_I2S_CTRL_LRCK_L_LOW (TEGRA_I2S_LRCK_LEFT_LOW << TEGRA_I2S_CTRL_LRCK_SHIFT) -#define TEGRA_I2S_CTRL_LRCK_R_LOW (TEGRA_I2S_LRCK_RIGHT_LOW << TEGRA_I2S_CTRL_LRCK_SHIFT) - -#define TEGRA_I2S_BIT_FORMAT_I2S 0 -#define TEGRA_I2S_BIT_FORMAT_RJM 1 -#define TEGRA_I2S_BIT_FORMAT_LJM 2 -#define TEGRA_I2S_BIT_FORMAT_DSP 3 - -#define TEGRA_I2S_CTRL_BIT_FORMAT_SHIFT 10 -#define TEGRA_I2S_CTRL_BIT_FORMAT_MASK (3 << TEGRA_I2S_CTRL_BIT_FORMAT_SHIFT) -#define TEGRA_I2S_CTRL_BIT_FORMAT_I2S (TEGRA_I2S_BIT_FORMAT_I2S << TEGRA_I2S_CTRL_BIT_FORMAT_SHIFT) -#define TEGRA_I2S_CTRL_BIT_FORMAT_RJM (TEGRA_I2S_BIT_FORMAT_RJM << TEGRA_I2S_CTRL_BIT_FORMAT_SHIFT) -#define TEGRA_I2S_CTRL_BIT_FORMAT_LJM (TEGRA_I2S_BIT_FORMAT_LJM << TEGRA_I2S_CTRL_BIT_FORMAT_SHIFT) -#define TEGRA_I2S_CTRL_BIT_FORMAT_DSP (TEGRA_I2S_BIT_FORMAT_DSP << TEGRA_I2S_CTRL_BIT_FORMAT_SHIFT) - -#define TEGRA_I2S_BIT_SIZE_16 0 -#define TEGRA_I2S_BIT_SIZE_20 1 -#define TEGRA_I2S_BIT_SIZE_24 2 -#define TEGRA_I2S_BIT_SIZE_32 3 - -#define TEGRA_I2S_CTRL_BIT_SIZE_SHIFT 8 -#define TEGRA_I2S_CTRL_BIT_SIZE_MASK (3 << TEGRA_I2S_CTRL_BIT_SIZE_SHIFT) -#define TEGRA_I2S_CTRL_BIT_SIZE_16 (TEGRA_I2S_BIT_SIZE_16 << TEGRA_I2S_CTRL_BIT_SIZE_SHIFT) -#define TEGRA_I2S_CTRL_BIT_SIZE_20 (TEGRA_I2S_BIT_SIZE_20 << TEGRA_I2S_CTRL_BIT_SIZE_SHIFT) -#define TEGRA_I2S_CTRL_BIT_SIZE_24 (TEGRA_I2S_BIT_SIZE_24 << TEGRA_I2S_CTRL_BIT_SIZE_SHIFT) -#define TEGRA_I2S_CTRL_BIT_SIZE_32 (TEGRA_I2S_BIT_SIZE_32 << TEGRA_I2S_CTRL_BIT_SIZE_SHIFT) - -#define TEGRA_I2S_FIFO_16_LSB 0 -#define TEGRA_I2S_FIFO_20_LSB 1 -#define TEGRA_I2S_FIFO_24_LSB 2 -#define TEGRA_I2S_FIFO_32 3 -#define TEGRA_I2S_FIFO_PACKED 7 - -#define TEGRA_I2S_CTRL_FIFO_FORMAT_SHIFT 4 -#define TEGRA_I2S_CTRL_FIFO_FORMAT_MASK (7 << TEGRA_I2S_CTRL_FIFO_FORMAT_SHIFT) -#define TEGRA_I2S_CTRL_FIFO_FORMAT_16_LSB (TEGRA_I2S_FIFO_16_LSB << TEGRA_I2S_CTRL_FIFO_FORMAT_SHIFT) -#define TEGRA_I2S_CTRL_FIFO_FORMAT_20_LSB (TEGRA_I2S_FIFO_20_LSB << TEGRA_I2S_CTRL_FIFO_FORMAT_SHIFT) -#define TEGRA_I2S_CTRL_FIFO_FORMAT_24_LSB (TEGRA_I2S_FIFO_24_LSB << TEGRA_I2S_CTRL_FIFO_FORMAT_SHIFT) -#define TEGRA_I2S_CTRL_FIFO_FORMAT_32 (TEGRA_I2S_FIFO_32 << TEGRA_I2S_CTRL_FIFO_FORMAT_SHIFT) -#define TEGRA_I2S_CTRL_FIFO_FORMAT_PACKED (TEGRA_I2S_FIFO_PACKED << TEGRA_I2S_CTRL_FIFO_FORMAT_SHIFT) - -#define TEGRA_I2S_CTRL_IE_FIFO1_ERR (1 << 3) -#define TEGRA_I2S_CTRL_IE_FIFO2_ERR (1 << 2) -#define TEGRA_I2S_CTRL_QE_FIFO1 (1 << 1) -#define TEGRA_I2S_CTRL_QE_FIFO2 (1 << 0) - -/* Fields in TEGRA_I2S_STATUS */ - -#define TEGRA_I2S_STATUS_FIFO1_RDY (1 << 31) -#define TEGRA_I2S_STATUS_FIFO2_RDY (1 << 30) -#define TEGRA_I2S_STATUS_FIFO1_BSY (1 << 29) -#define TEGRA_I2S_STATUS_FIFO2_BSY (1 << 28) -#define TEGRA_I2S_STATUS_FIFO1_ERR (1 << 3) -#define TEGRA_I2S_STATUS_FIFO2_ERR (1 << 2) -#define TEGRA_I2S_STATUS_QS_FIFO1 (1 << 1) -#define TEGRA_I2S_STATUS_QS_FIFO2 (1 << 0) - -/* Fields in TEGRA_I2S_TIMING */ - -#define TEGRA_I2S_TIMING_NON_SYM_ENABLE (1 << 12) -#define TEGRA_I2S_TIMING_CHANNEL_BIT_COUNT_SHIFT 0 -#define TEGRA_I2S_TIMING_CHANNEL_BIT_COUNT_MASK_US 0x7fff -#define TEGRA_I2S_TIMING_CHANNEL_BIT_COUNT_MASK (TEGRA_I2S_TIMING_CHANNEL_BIT_COUNT_MASK_US << TEGRA_I2S_TIMING_CHANNEL_BIT_COUNT_SHIFT) - -/* Fields in TEGRA_I2S_FIFO_SCR */ - -#define TEGRA_I2S_FIFO_SCR_FIFO2_FULL_EMPTY_COUNT_SHIFT 24 -#define TEGRA_I2S_FIFO_SCR_FIFO1_FULL_EMPTY_COUNT_SHIFT 16 -#define TEGRA_I2S_FIFO_SCR_FIFO_FULL_EMPTY_COUNT_MASK 0x3f - -#define TEGRA_I2S_FIFO_SCR_FIFO2_CLR (1 << 12) -#define TEGRA_I2S_FIFO_SCR_FIFO1_CLR (1 << 8) - -#define TEGRA_I2S_FIFO_ATN_LVL_ONE_SLOT 0 -#define TEGRA_I2S_FIFO_ATN_LVL_FOUR_SLOTS 1 -#define TEGRA_I2S_FIFO_ATN_LVL_EIGHT_SLOTS 2 -#define TEGRA_I2S_FIFO_ATN_LVL_TWELVE_SLOTS 3 - -#define TEGRA_I2S_FIFO_SCR_FIFO2_ATN_LVL_SHIFT 4 -#define TEGRA_I2S_FIFO_SCR_FIFO2_ATN_LVL_MASK (3 << TEGRA_I2S_FIFO_SCR_FIFO2_ATN_LVL_SHIFT) -#define TEGRA_I2S_FIFO_SCR_FIFO2_ATN_LVL_ONE_SLOT (TEGRA_I2S_FIFO_ATN_LVL_ONE_SLOT << TEGRA_I2S_FIFO_SCR_FIFO2_ATN_LVL_SHIFT) -#define TEGRA_I2S_FIFO_SCR_FIFO2_ATN_LVL_FOUR_SLOTS (TEGRA_I2S_FIFO_ATN_LVL_FOUR_SLOTS << TEGRA_I2S_FIFO_SCR_FIFO2_ATN_LVL_SHIFT) -#define TEGRA_I2S_FIFO_SCR_FIFO2_ATN_LVL_EIGHT_SLOTS (TEGRA_I2S_FIFO_ATN_LVL_EIGHT_SLOTS << TEGRA_I2S_FIFO_SCR_FIFO2_ATN_LVL_SHIFT) -#define TEGRA_I2S_FIFO_SCR_FIFO2_ATN_LVL_TWELVE_SLOTS (TEGRA_I2S_FIFO_ATN_LVL_TWELVE_SLOTS << TEGRA_I2S_FIFO_SCR_FIFO2_ATN_LVL_SHIFT) - -#define TEGRA_I2S_FIFO_SCR_FIFO1_ATN_LVL_SHIFT 0 -#define TEGRA_I2S_FIFO_SCR_FIFO1_ATN_LVL_MASK (3 << TEGRA_I2S_FIFO_SCR_FIFO1_ATN_LVL_SHIFT) -#define TEGRA_I2S_FIFO_SCR_FIFO1_ATN_LVL_ONE_SLOT (TEGRA_I2S_FIFO_ATN_LVL_ONE_SLOT << TEGRA_I2S_FIFO_SCR_FIFO1_ATN_LVL_SHIFT) -#define TEGRA_I2S_FIFO_SCR_FIFO1_ATN_LVL_FOUR_SLOTS (TEGRA_I2S_FIFO_ATN_LVL_FOUR_SLOTS << TEGRA_I2S_FIFO_SCR_FIFO1_ATN_LVL_SHIFT) -#define TEGRA_I2S_FIFO_SCR_FIFO1_ATN_LVL_EIGHT_SLOTS (TEGRA_I2S_FIFO_ATN_LVL_EIGHT_SLOTS << TEGRA_I2S_FIFO_SCR_FIFO1_ATN_LVL_SHIFT) -#define TEGRA_I2S_FIFO_SCR_FIFO1_ATN_LVL_TWELVE_SLOTS (TEGRA_I2S_FIFO_ATN_LVL_TWELVE_SLOTS << TEGRA_I2S_FIFO_SCR_FIFO1_ATN_LVL_SHIFT) - -struct tegra_i2s { - struct snd_soc_dai_driver dai; - struct clk *clk_i2s; - int clk_refs; - struct tegra_pcm_dma_params capture_dma_data; - struct tegra_pcm_dma_params playback_dma_data; - void __iomem *regs; - struct dentry *debug; - u32 reg_ctrl; -}; - -#endif diff --git a/sound/soc/tegra/tegra_pcm.c b/sound/soc/tegra/tegra_pcm.c index 8b4457137c7..127348dc09b 100644 --- a/sound/soc/tegra/tegra_pcm.c +++ b/sound/soc/tegra/tegra_pcm.c @@ -2,7 +2,7 @@ * tegra_pcm.c - Tegra PCM driver * * Author: Stephen Warren <swarren@nvidia.com> - * Copyright (C) 2010 - NVIDIA, Inc. + * Copyright (C) 2010,2012 - NVIDIA, Inc. * * Based on code copyright/by: * @@ -29,8 +29,8 @@ * */ -#include <linux/module.h> #include <linux/dma-mapping.h> +#include <linux/module.h> #include <linux/slab.h> #include <sound/core.h> #include <sound/pcm.h> @@ -39,8 +39,6 @@ #include "tegra_pcm.h" -#define DRV_NAME "tegra-pcm-audio" - static const struct snd_pcm_hardware tegra_pcm_hardware = { .info = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID | @@ -372,28 +370,18 @@ static struct snd_soc_platform_driver tegra_pcm_platform = { .pcm_free = tegra_pcm_free, }; -static int __devinit tegra_pcm_platform_probe(struct platform_device *pdev) +int __devinit tegra_pcm_platform_register(struct device *dev) { - return snd_soc_register_platform(&pdev->dev, &tegra_pcm_platform); + return snd_soc_register_platform(dev, &tegra_pcm_platform); } +EXPORT_SYMBOL_GPL(tegra_pcm_platform_register); -static int __devexit tegra_pcm_platform_remove(struct platform_device *pdev) +void __devexit tegra_pcm_platform_unregister(struct device *dev) { - snd_soc_unregister_platform(&pdev->dev); - return 0; + snd_soc_unregister_platform(dev); } - -static struct platform_driver tegra_pcm_driver = { - .driver = { - .name = DRV_NAME, - .owner = THIS_MODULE, - }, - .probe = tegra_pcm_platform_probe, - .remove = __devexit_p(tegra_pcm_platform_remove), -}; -module_platform_driver(tegra_pcm_driver); +EXPORT_SYMBOL_GPL(tegra_pcm_platform_unregister); MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>"); MODULE_DESCRIPTION("Tegra PCM ASoC driver"); MODULE_LICENSE("GPL"); -MODULE_ALIAS("platform:" DRV_NAME); diff --git a/sound/soc/tegra/tegra_pcm.h b/sound/soc/tegra/tegra_pcm.h index dbb90339fe0..985d418a35e 100644 --- a/sound/soc/tegra/tegra_pcm.h +++ b/sound/soc/tegra/tegra_pcm.h @@ -2,7 +2,7 @@ * tegra_pcm.h - Definitions for Tegra PCM driver * * Author: Stephen Warren <swarren@nvidia.com> - * Copyright (C) 2010 - NVIDIA, Inc. + * Copyright (C) 2010,2012 - NVIDIA, Inc. * * Based on code copyright/by: * @@ -52,4 +52,7 @@ struct tegra_runtime_data { struct tegra_dma_channel *dma_chan; }; +int tegra_pcm_platform_register(struct device *dev); +void tegra_pcm_platform_unregister(struct device *dev); + #endif diff --git a/sound/soc/tegra/tegra_spdif.c b/sound/soc/tegra/tegra_spdif.c deleted file mode 100644 index 9ff2c601445..00000000000 --- a/sound/soc/tegra/tegra_spdif.c +++ /dev/null @@ -1,364 +0,0 @@ -/* - * tegra_spdif.c - Tegra SPDIF driver - * - * Author: Stephen Warren <swarren@nvidia.com> - * Copyright (C) 2011 - NVIDIA, Inc. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * version 2 as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * 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/clk.h> -#include <linux/module.h> -#include <linux/debugfs.h> -#include <linux/device.h> -#include <linux/platform_device.h> -#include <linux/seq_file.h> -#include <linux/slab.h> -#include <linux/io.h> -#include <mach/iomap.h> -#include <sound/core.h> -#include <sound/pcm.h> -#include <sound/pcm_params.h> -#include <sound/soc.h> - -#include "tegra_spdif.h" - -#define DRV_NAME "tegra-spdif" - -static inline void tegra_spdif_write(struct tegra_spdif *spdif, u32 reg, - u32 val) -{ - __raw_writel(val, spdif->regs + reg); -} - -static inline u32 tegra_spdif_read(struct tegra_spdif *spdif, u32 reg) -{ - return __raw_readl(spdif->regs + reg); -} - -#ifdef CONFIG_DEBUG_FS -static int tegra_spdif_show(struct seq_file *s, void *unused) -{ -#define REG(r) { r, #r } - static const struct { - int offset; - const char *name; - } regs[] = { - REG(TEGRA_SPDIF_CTRL), - REG(TEGRA_SPDIF_STATUS), - REG(TEGRA_SPDIF_STROBE_CTRL), - REG(TEGRA_SPDIF_DATA_FIFO_CSR), - REG(TEGRA_SPDIF_CH_STA_RX_A), - REG(TEGRA_SPDIF_CH_STA_RX_B), - REG(TEGRA_SPDIF_CH_STA_RX_C), - REG(TEGRA_SPDIF_CH_STA_RX_D), - REG(TEGRA_SPDIF_CH_STA_RX_E), - REG(TEGRA_SPDIF_CH_STA_RX_F), - REG(TEGRA_SPDIF_CH_STA_TX_A), - REG(TEGRA_SPDIF_CH_STA_TX_B), - REG(TEGRA_SPDIF_CH_STA_TX_C), - REG(TEGRA_SPDIF_CH_STA_TX_D), - REG(TEGRA_SPDIF_CH_STA_TX_E), - REG(TEGRA_SPDIF_CH_STA_TX_F), - }; -#undef REG - - struct tegra_spdif *spdif = s->private; - int i; - - clk_enable(spdif->clk_spdif_out); - - for (i = 0; i < ARRAY_SIZE(regs); i++) { - u32 val = tegra_spdif_read(spdif, regs[i].offset); - seq_printf(s, "%s = %08x\n", regs[i].name, val); - } - - clk_disable(spdif->clk_spdif_out); - - return 0; -} - -static int tegra_spdif_debug_open(struct inode *inode, struct file *file) -{ - return single_open(file, tegra_spdif_show, inode->i_private); -} - -static const struct file_operations tegra_spdif_debug_fops = { - .open = tegra_spdif_debug_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, -}; - -static void tegra_spdif_debug_add(struct tegra_spdif *spdif) -{ - spdif->debug = debugfs_create_file(DRV_NAME, S_IRUGO, - snd_soc_debugfs_root, spdif, - &tegra_spdif_debug_fops); -} - -static void tegra_spdif_debug_remove(struct tegra_spdif *spdif) -{ - if (spdif->debug) - debugfs_remove(spdif->debug); -} -#else -static inline void tegra_spdif_debug_add(struct tegra_spdif *spdif) -{ -} - -static inline void tegra_spdif_debug_remove(struct tegra_spdif *spdif) -{ -} -#endif - -static int tegra_spdif_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *params, - struct snd_soc_dai *dai) -{ - struct device *dev = substream->pcm->card->dev; - struct tegra_spdif *spdif = snd_soc_dai_get_drvdata(dai); - int ret, spdifclock; - - spdif->reg_ctrl &= ~TEGRA_SPDIF_CTRL_PACK; - spdif->reg_ctrl &= ~TEGRA_SPDIF_CTRL_BIT_MODE_MASK; - switch (params_format(params)) { - case SNDRV_PCM_FORMAT_S16_LE: - spdif->reg_ctrl |= TEGRA_SPDIF_CTRL_PACK; - spdif->reg_ctrl |= TEGRA_SPDIF_CTRL_BIT_MODE_16BIT; - break; - default: - return -EINVAL; - } - - switch (params_rate(params)) { - case 32000: - spdifclock = 4096000; - break; - case 44100: - spdifclock = 5644800; - break; - case 48000: - spdifclock = 6144000; - break; - case 88200: - spdifclock = 11289600; - break; - case 96000: - spdifclock = 12288000; - break; - case 176400: - spdifclock = 22579200; - break; - case 192000: - spdifclock = 24576000; - break; - default: - return -EINVAL; - } - - ret = clk_set_rate(spdif->clk_spdif_out, spdifclock); - if (ret) { - dev_err(dev, "Can't set SPDIF clock rate: %d\n", ret); - return ret; - } - - return 0; -} - -static void tegra_spdif_start_playback(struct tegra_spdif *spdif) -{ - spdif->reg_ctrl |= TEGRA_SPDIF_CTRL_TX_EN; - tegra_spdif_write(spdif, TEGRA_SPDIF_CTRL, spdif->reg_ctrl); -} - -static void tegra_spdif_stop_playback(struct tegra_spdif *spdif) -{ - spdif->reg_ctrl &= ~TEGRA_SPDIF_CTRL_TX_EN; - tegra_spdif_write(spdif, TEGRA_SPDIF_CTRL, spdif->reg_ctrl); -} - -static int tegra_spdif_trigger(struct snd_pcm_substream *substream, int cmd, - struct snd_soc_dai *dai) -{ - struct tegra_spdif *spdif = snd_soc_dai_get_drvdata(dai); - - switch (cmd) { - case SNDRV_PCM_TRIGGER_START: - case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: - case SNDRV_PCM_TRIGGER_RESUME: - if (!spdif->clk_refs) - clk_enable(spdif->clk_spdif_out); - spdif->clk_refs++; - tegra_spdif_start_playback(spdif); - break; - case SNDRV_PCM_TRIGGER_STOP: - case SNDRV_PCM_TRIGGER_PAUSE_PUSH: - case SNDRV_PCM_TRIGGER_SUSPEND: - tegra_spdif_stop_playback(spdif); - spdif->clk_refs--; - if (!spdif->clk_refs) - clk_disable(spdif->clk_spdif_out); - break; - default: - return -EINVAL; - } - - return 0; -} - -static int tegra_spdif_probe(struct snd_soc_dai *dai) -{ - struct tegra_spdif *spdif = snd_soc_dai_get_drvdata(dai); - - dai->capture_dma_data = NULL; - dai->playback_dma_data = &spdif->playback_dma_data; - - return 0; -} - -static const struct snd_soc_dai_ops tegra_spdif_dai_ops = { - .hw_params = tegra_spdif_hw_params, - .trigger = tegra_spdif_trigger, -}; - -static struct snd_soc_dai_driver tegra_spdif_dai = { - .name = DRV_NAME, - .probe = tegra_spdif_probe, - .playback = { - .channels_min = 2, - .channels_max = 2, - .rates = SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | - SNDRV_PCM_RATE_48000, - .formats = SNDRV_PCM_FMTBIT_S16_LE, - }, - .ops = &tegra_spdif_dai_ops, -}; - -static __devinit int tegra_spdif_platform_probe(struct platform_device *pdev) -{ - struct tegra_spdif *spdif; - struct resource *mem, *memregion, *dmareq; - int ret; - - spdif = kzalloc(sizeof(struct tegra_spdif), GFP_KERNEL); - if (!spdif) { - dev_err(&pdev->dev, "Can't allocate tegra_spdif\n"); - ret = -ENOMEM; - goto exit; - } - dev_set_drvdata(&pdev->dev, spdif); - - spdif->clk_spdif_out = clk_get(&pdev->dev, "spdif_out"); - if (IS_ERR(spdif->clk_spdif_out)) { - pr_err("Can't retrieve spdif clock\n"); - ret = PTR_ERR(spdif->clk_spdif_out); - goto err_free; - } - - mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!mem) { - dev_err(&pdev->dev, "No memory resource\n"); - ret = -ENODEV; - goto err_clk_put; - } - - dmareq = platform_get_resource(pdev, IORESOURCE_DMA, 0); - if (!dmareq) { - dev_err(&pdev->dev, "No DMA resource\n"); - ret = -ENODEV; - goto err_clk_put; - } - - memregion = request_mem_region(mem->start, resource_size(mem), - DRV_NAME); - if (!memregion) { - dev_err(&pdev->dev, "Memory region already claimed\n"); - ret = -EBUSY; - goto err_clk_put; - } - - spdif->regs = ioremap(mem->start, resource_size(mem)); - if (!spdif->regs) { - dev_err(&pdev->dev, "ioremap failed\n"); - ret = -ENOMEM; - goto err_release; - } - - spdif->playback_dma_data.addr = mem->start + TEGRA_SPDIF_DATA_OUT; - spdif->playback_dma_data.wrap = 4; - spdif->playback_dma_data.width = 32; - spdif->playback_dma_data.req_sel = dmareq->start; - - ret = snd_soc_register_dai(&pdev->dev, &tegra_spdif_dai); - if (ret) { - dev_err(&pdev->dev, "Could not register DAI: %d\n", ret); - ret = -ENOMEM; - goto err_unmap; - } - - tegra_spdif_debug_add(spdif); - - return 0; - -err_unmap: - iounmap(spdif->regs); -err_release: - release_mem_region(mem->start, resource_size(mem)); -err_clk_put: - clk_put(spdif->clk_spdif_out); -err_free: - kfree(spdif); -exit: - return ret; -} - -static int __devexit tegra_spdif_platform_remove(struct platform_device *pdev) -{ - struct tegra_spdif *spdif = dev_get_drvdata(&pdev->dev); - struct resource *res; - - snd_soc_unregister_dai(&pdev->dev); - - tegra_spdif_debug_remove(spdif); - - iounmap(spdif->regs); - - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - release_mem_region(res->start, resource_size(res)); - - clk_put(spdif->clk_spdif_out); - - kfree(spdif); - - return 0; -} - -static struct platform_driver tegra_spdif_driver = { - .driver = { - .name = DRV_NAME, - .owner = THIS_MODULE, - }, - .probe = tegra_spdif_platform_probe, - .remove = __devexit_p(tegra_spdif_platform_remove), -}; - -module_platform_driver(tegra_spdif_driver); - -MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>"); -MODULE_DESCRIPTION("Tegra SPDIF ASoC driver"); -MODULE_LICENSE("GPL"); -MODULE_ALIAS("platform:" DRV_NAME); diff --git a/sound/soc/tegra/tegra_spdif.h b/sound/soc/tegra/tegra_spdif.h deleted file mode 100644 index 2e03db43027..00000000000 --- a/sound/soc/tegra/tegra_spdif.h +++ /dev/null @@ -1,473 +0,0 @@ -/* - * tegra_spdif.h - Definitions for Tegra SPDIF driver - * - * Author: Stephen Warren <swarren@nvidia.com> - * Copyright (C) 2011 - NVIDIA, Inc. - * - * Based on code copyright/by: - * Copyright (c) 2008-2009, NVIDIA Corporation - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * version 2 as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA - * 02110-1301 USA - * - */ - -#ifndef __TEGRA_SPDIF_H__ -#define __TEGRA_SPDIF_H__ - -#include "tegra_pcm.h" - -/* Offsets from TEGRA_SPDIF_BASE */ - -#define TEGRA_SPDIF_CTRL 0x0 -#define TEGRA_SPDIF_STATUS 0x4 -#define TEGRA_SPDIF_STROBE_CTRL 0x8 -#define TEGRA_SPDIF_DATA_FIFO_CSR 0x0C -#define TEGRA_SPDIF_DATA_OUT 0x40 -#define TEGRA_SPDIF_DATA_IN 0x80 -#define TEGRA_SPDIF_CH_STA_RX_A 0x100 -#define TEGRA_SPDIF_CH_STA_RX_B 0x104 -#define TEGRA_SPDIF_CH_STA_RX_C 0x108 -#define TEGRA_SPDIF_CH_STA_RX_D 0x10C -#define TEGRA_SPDIF_CH_STA_RX_E 0x110 -#define TEGRA_SPDIF_CH_STA_RX_F 0x114 -#define TEGRA_SPDIF_CH_STA_TX_A 0x140 -#define TEGRA_SPDIF_CH_STA_TX_B 0x144 -#define TEGRA_SPDIF_CH_STA_TX_C 0x148 -#define TEGRA_SPDIF_CH_STA_TX_D 0x14C -#define TEGRA_SPDIF_CH_STA_TX_E 0x150 -#define TEGRA_SPDIF_CH_STA_TX_F 0x154 -#define TEGRA_SPDIF_USR_STA_RX_A 0x180 -#define TEGRA_SPDIF_USR_DAT_TX_A 0x1C0 - -/* Fields in TEGRA_SPDIF_CTRL */ - -/* Start capturing from 0=right, 1=left channel */ -#define TEGRA_SPDIF_CTRL_CAP_LC (1 << 30) - -/* SPDIF receiver(RX) enable */ -#define TEGRA_SPDIF_CTRL_RX_EN (1 << 29) - -/* SPDIF Transmitter(TX) enable */ -#define TEGRA_SPDIF_CTRL_TX_EN (1 << 28) - -/* Transmit Channel status */ -#define TEGRA_SPDIF_CTRL_TC_EN (1 << 27) - -/* Transmit user Data */ -#define TEGRA_SPDIF_CTRL_TU_EN (1 << 26) - -/* Interrupt on transmit error */ -#define TEGRA_SPDIF_CTRL_IE_TXE (1 << 25) - -/* Interrupt on receive error */ -#define TEGRA_SPDIF_CTRL_IE_RXE (1 << 24) - -/* Interrupt on invalid preamble */ -#define TEGRA_SPDIF_CTRL_IE_P (1 << 23) - -/* Interrupt on "B" preamble */ -#define TEGRA_SPDIF_CTRL_IE_B (1 << 22) - -/* Interrupt when block of channel status received */ -#define TEGRA_SPDIF_CTRL_IE_C (1 << 21) - -/* Interrupt when a valid information unit (IU) is received */ -#define TEGRA_SPDIF_CTRL_IE_U (1 << 20) - -/* Interrupt when RX user FIFO attention level is reached */ -#define TEGRA_SPDIF_CTRL_QE_RU (1 << 19) - -/* Interrupt when TX user FIFO attention level is reached */ -#define TEGRA_SPDIF_CTRL_QE_TU (1 << 18) - -/* Interrupt when RX data FIFO attention level is reached */ -#define TEGRA_SPDIF_CTRL_QE_RX (1 << 17) - -/* Interrupt when TX data FIFO attention level is reached */ -#define TEGRA_SPDIF_CTRL_QE_TX (1 << 16) - -/* Loopback test mode enable */ -#define TEGRA_SPDIF_CTRL_LBK_EN (1 << 15) - -/* - * Pack data mode: - * 0 = Single data (16 bit needs to be padded to match the - * interface data bit size). - * 1 = Packeted left/right channel data into a single word. - */ -#define TEGRA_SPDIF_CTRL_PACK (1 << 14) - -/* - * 00 = 16bit data - * 01 = 20bit data - * 10 = 24bit data - * 11 = raw data - */ -#define TEGRA_SPDIF_BIT_MODE_16BIT 0 -#define TEGRA_SPDIF_BIT_MODE_20BIT 1 -#define TEGRA_SPDIF_BIT_MODE_24BIT 2 -#define TEGRA_SPDIF_BIT_MODE_RAW 3 - -#define TEGRA_SPDIF_CTRL_BIT_MODE_SHIFT 12 -#define TEGRA_SPDIF_CTRL_BIT_MODE_MASK (3 << TEGRA_SPDIF_CTRL_BIT_MODE_SHIFT) -#define TEGRA_SPDIF_CTRL_BIT_MODE_16BIT (TEGRA_SPDIF_BIT_MODE_16BIT << TEGRA_SPDIF_CTRL_BIT_MODE_SHIFT) -#define TEGRA_SPDIF_CTRL_BIT_MODE_20BIT (TEGRA_SPDIF_BIT_MODE_20BIT << TEGRA_SPDIF_CTRL_BIT_MODE_SHIFT) -#define TEGRA_SPDIF_CTRL_BIT_MODE_24BIT (TEGRA_SPDIF_BIT_MODE_24BIT << TEGRA_SPDIF_CTRL_BIT_MODE_SHIFT) -#define TEGRA_SPDIF_CTRL_BIT_MODE_RAW (TEGRA_SPDIF_BIT_MODE_RAW << TEGRA_SPDIF_CTRL_BIT_MODE_SHIFT) - -/* Fields in TEGRA_SPDIF_STATUS */ - -/* - * Note: IS_P, IS_B, IS_C, and IS_U are sticky bits. Software must - * write a 1 to the corresponding bit location to clear the status. - */ - -/* - * Receiver(RX) shifter is busy receiving data. - * This bit is asserted when the receiver first locked onto the - * preamble of the data stream after RX_EN is asserted. This bit is - * deasserted when either, - * (a) the end of a frame is reached after RX_EN is deeasserted, or - * (b) the SPDIF data stream becomes inactive. - */ -#define TEGRA_SPDIF_STATUS_RX_BSY (1 << 29) - -/* - * Transmitter(TX) shifter is busy transmitting data. - * This bit is asserted when TX_EN is asserted. - * This bit is deasserted when the end of a frame is reached after - * TX_EN is deasserted. - */ -#define TEGRA_SPDIF_STATUS_TX_BSY (1 << 28) - -/* - * TX is busy shifting out channel status. - * This bit is asserted when both TX_EN and TC_EN are asserted and - * data from CH_STA_TX_A register is loaded into the internal shifter. - * This bit is deasserted when either, - * (a) the end of a frame is reached after TX_EN is deasserted, or - * (b) CH_STA_TX_F register is loaded into the internal shifter. - */ -#define TEGRA_SPDIF_STATUS_TC_BSY (1 << 27) - -/* - * TX User data FIFO busy. - * This bit is asserted when TX_EN and TXU_EN are asserted and - * there's data in the TX user FIFO. This bit is deassert when either, - * (a) the end of a frame is reached after TX_EN is deasserted, or - * (b) there's no data left in the TX user FIFO. - */ -#define TEGRA_SPDIF_STATUS_TU_BSY (1 << 26) - -/* TX FIFO Underrun error status */ -#define TEGRA_SPDIF_STATUS_TX_ERR (1 << 25) - -/* RX FIFO Overrun error status */ -#define TEGRA_SPDIF_STATUS_RX_ERR (1 << 24) - -/* Preamble status: 0=Preamble OK, 1=bad/missing preamble */ -#define TEGRA_SPDIF_STATUS_IS_P (1 << 23) - -/* B-preamble detection status: 0=not detected, 1=B-preamble detected */ -#define TEGRA_SPDIF_STATUS_IS_B (1 << 22) - -/* - * RX channel block data receive status: - * 0=entire block not recieved yet. - * 1=received entire block of channel status, - */ -#define TEGRA_SPDIF_STATUS_IS_C (1 << 21) - -/* RX User Data Valid flag: 1=valid IU detected, 0 = no IU detected. */ -#define TEGRA_SPDIF_STATUS_IS_U (1 << 20) - -/* - * RX User FIFO Status: - * 1=attention level reached, 0=attention level not reached. - */ -#define TEGRA_SPDIF_STATUS_QS_RU (1 << 19) - -/* - * TX User FIFO Status: - * 1=attention level reached, 0=attention level not reached. - */ -#define TEGRA_SPDIF_STATUS_QS_TU (1 << 18) - -/* - * RX Data FIFO Status: - * 1=attention level reached, 0=attention level not reached. - */ -#define TEGRA_SPDIF_STATUS_QS_RX (1 << 17) - -/* - * TX Data FIFO Status: - * 1=attention level reached, 0=attention level not reached. - */ -#define TEGRA_SPDIF_STATUS_QS_TX (1 << 16) - -/* Fields in TEGRA_SPDIF_STROBE_CTRL */ - -/* - * Indicates the approximate number of detected SPDIFIN clocks within a - * bi-phase period. - */ -#define TEGRA_SPDIF_STROBE_CTRL_PERIOD_SHIFT 16 -#define TEGRA_SPDIF_STROBE_CTRL_PERIOD_MASK (0xff << TEGRA_SPDIF_STROBE_CTRL_PERIOD_SHIFT) - -/* Data strobe mode: 0=Auto-locked 1=Manual locked */ -#define TEGRA_SPDIF_STROBE_CTRL_STROBE (1 << 15) - -/* - * Manual data strobe time within the bi-phase clock period (in terms of - * the number of over-sampling clocks). - */ -#define TEGRA_SPDIF_STROBE_CTRL_DATA_STROBES_SHIFT 8 -#define TEGRA_SPDIF_STROBE_CTRL_DATA_STROBES_MASK (0x1f << TEGRA_SPDIF_STROBE_CTRL_DATA_STROBES_SHIFT) - -/* - * Manual SPDIFIN bi-phase clock period (in terms of the number of - * over-sampling clocks). - */ -#define TEGRA_SPDIF_STROBE_CTRL_CLOCK_PERIOD_SHIFT 0 -#define TEGRA_SPDIF_STROBE_CTRL_CLOCK_PERIOD_MASK (0x3f << TEGRA_SPDIF_STROBE_CTRL_CLOCK_PERIOD_SHIFT) - -/* Fields in SPDIF_DATA_FIFO_CSR */ - -/* Clear Receiver User FIFO (RX USR.FIFO) */ -#define TEGRA_SPDIF_DATA_FIFO_CSR_RU_CLR (1 << 31) - -#define TEGRA_SPDIF_FIFO_ATN_LVL_U_ONE_SLOT 0 -#define TEGRA_SPDIF_FIFO_ATN_LVL_U_TWO_SLOTS 1 -#define TEGRA_SPDIF_FIFO_ATN_LVL_U_THREE_SLOTS 2 -#define TEGRA_SPDIF_FIFO_ATN_LVL_U_FOUR_SLOTS 3 - -/* RU FIFO attention level */ -#define TEGRA_SPDIF_DATA_FIFO_CSR_RU_ATN_LVL_SHIFT 29 -#define TEGRA_SPDIF_DATA_FIFO_CSR_RU_ATN_LVL_MASK \ - (0x3 << TEGRA_SPDIF_DATA_FIFO_CSR_RU_ATN_LVL_SHIFT) -#define TEGRA_SPDIF_DATA_FIFO_CSR_RU_ATN_LVL_RU1_WORD_FULL \ - (TEGRA_SPDIF_FIFO_ATN_LVL_U_ONE_SLOT << TEGRA_SPDIF_DATA_FIFO_CSR_RU_ATN_LVL_SHIFT) -#define TEGRA_SPDIF_DATA_FIFO_CSR_RU_ATN_LVL_RU2_WORD_FULL \ - (TEGRA_SPDIF_FIFO_ATN_LVL_U_TWO_SLOTS << TEGRA_SPDIF_DATA_FIFO_CSR_RU_ATN_LVL_SHIFT) -#define TEGRA_SPDIF_DATA_FIFO_CSR_RU_ATN_LVL_RU3_WORD_FULL \ - (TEGRA_SPDIF_FIFO_ATN_LVL_U_THREE_SLOTS << TEGRA_SPDIF_DATA_FIFO_CSR_RU_ATN_LVL_SHIFT) -#define TEGRA_SPDIF_DATA_FIFO_CSR_RU_ATN_LVL_RU4_WORD_FULL \ - (TEGRA_SPDIF_FIFO_ATN_LVL_U_FOUR_SLOTS << TEGRA_SPDIF_DATA_FIFO_CSR_RU_ATN_LVL_SHIFT) - -/* Number of RX USR.FIFO levels with valid data. */ -#define TEGRA_SPDIF_DATA_FIFO_CSR_RU_FULL_COUNT_SHIFT 24 -#define TEGRA_SPDIF_DATA_FIFO_CSR_RU_FULL_COUNT_MASK (0x1f << TEGRA_SPDIF_DATA_FIFO_CSR_RU_FULL_COUNT_SHIFT) - -/* Clear Transmitter User FIFO (TX USR.FIFO) */ -#define TEGRA_SPDIF_DATA_FIFO_CSR_TU_CLR (1 << 23) - -/* TU FIFO attention level */ -#define TEGRA_SPDIF_DATA_FIFO_CSR_TU_ATN_LVL_SHIFT 21 -#define TEGRA_SPDIF_DATA_FIFO_CSR_TU_ATN_LVL_MASK \ - (0x3 << TEGRA_SPDIF_DATA_FIFO_CSR_TU_ATN_LVL_SHIFT) -#define TEGRA_SPDIF_DATA_FIFO_CSR_TU_ATN_LVL_TU1_WORD_FULL \ - (TEGRA_SPDIF_FIFO_ATN_LVL_U_ONE_SLOT << TEGRA_SPDIF_DATA_FIFO_CSR_TU_ATN_LVL_SHIFT) -#define TEGRA_SPDIF_DATA_FIFO_CSR_TU_ATN_LVL_TU2_WORD_FULL \ - (TEGRA_SPDIF_FIFO_ATN_LVL_U_TWO_SLOTS << TEGRA_SPDIF_DATA_FIFO_CSR_TU_ATN_LVL_SHIFT) -#define TEGRA_SPDIF_DATA_FIFO_CSR_TU_ATN_LVL_TU3_WORD_FULL \ - (TEGRA_SPDIF_FIFO_ATN_LVL_U_THREE_SLOTS << TEGRA_SPDIF_DATA_FIFO_CSR_TU_ATN_LVL_SHIFT) -#define TEGRA_SPDIF_DATA_FIFO_CSR_TU_ATN_LVL_TU4_WORD_FULL \ - (TEGRA_SPDIF_FIFO_ATN_LVL_U_FOUR_SLOTS << TEGRA_SPDIF_DATA_FIFO_CSR_TU_ATN_LVL_SHIFT) - -/* Number of TX USR.FIFO levels that could be filled. */ -#define TEGRA_SPDIF_DATA_FIFO_CSR_TU_EMPTY_COUNT_SHIFT 16 -#define TEGRA_SPDIF_DATA_FIFO_CSR_TU_EMPTY_COUNT_MASK (0x1f << SPDIF_DATA_FIFO_CSR_TU_EMPTY_COUNT_SHIFT) - -/* Clear Receiver Data FIFO (RX DATA.FIFO) */ -#define TEGRA_SPDIF_DATA_FIFO_CSR_RX_CLR (1 << 15) - -#define TEGRA_SPDIF_FIFO_ATN_LVL_D_ONE_SLOT 0 -#define TEGRA_SPDIF_FIFO_ATN_LVL_D_FOUR_SLOTS 1 -#define TEGRA_SPDIF_FIFO_ATN_LVL_D_EIGHT_SLOTS 2 -#define TEGRA_SPDIF_FIFO_ATN_LVL_D_TWELVE_SLOTS 3 - -/* RU FIFO attention level */ -#define TEGRA_SPDIF_DATA_FIFO_CSR_RX_ATN_LVL_SHIFT 13 -#define TEGRA_SPDIF_DATA_FIFO_CSR_RX_ATN_LVL_MASK \ - (0x3 << TEGRA_SPDIF_DATA_FIFO_CSR_RX_ATN_LVL_SHIFT) -#define TEGRA_SPDIF_DATA_FIFO_CSR_RX_ATN_LVL_RU1_WORD_FULL \ - (TEGRA_SPDIF_FIFO_ATN_LVL_D_ONE_SLOT << TEGRA_SPDIF_DATA_FIFO_CSR_RX_ATN_LVL_SHIFT) -#define TEGRA_SPDIF_DATA_FIFO_CSR_RX_ATN_LVL_RU4_WORD_FULL \ - (TEGRA_SPDIF_FIFO_ATN_LVL_D_FOUR_SLOTS << TEGRA_SPDIF_DATA_FIFO_CSR_RX_ATN_LVL_SHIFT) -#define TEGRA_SPDIF_DATA_FIFO_CSR_RX_ATN_LVL_RU8_WORD_FULL \ - (TEGRA_SPDIF_FIFO_ATN_LVL_D_EIGHT_SLOTS << TEGRA_SPDIF_DATA_FIFO_CSR_RX_ATN_LVL_SHIFT) -#define TEGRA_SPDIF_DATA_FIFO_CSR_RX_ATN_LVL_RU12_WORD_FULL \ - (TEGRA_SPDIF_FIFO_ATN_LVL_D_TWELVE_SLOTS << TEGRA_SPDIF_DATA_FIFO_CSR_RX_ATN_LVL_SHIFT) - -/* Number of RX DATA.FIFO levels with valid data. */ -#define TEGRA_SPDIF_DATA_FIFO_CSR_RX_FULL_COUNT_SHIFT 8 -#define TEGRA_SPDIF_DATA_FIFO_CSR_RX_FULL_COUNT_MASK (0x1f << TEGRA_SPDIF_DATA_FIFO_CSR_RX_FULL_COUNT_SHIFT) - -/* Clear Transmitter Data FIFO (TX DATA.FIFO) */ -#define TEGRA_SPDIF_DATA_FIFO_CSR_TX_CLR (1 << 7) - -/* TU FIFO attention level */ -#define TEGRA_SPDIF_DATA_FIFO_CSR_TX_ATN_LVL_SHIFT 5 -#define TEGRA_SPDIF_DATA_FIFO_CSR_TX_ATN_LVL_MASK \ - (0x3 << TEGRA_SPDIF_DATA_FIFO_CSR_TX_ATN_LVL_SHIFT) -#define TEGRA_SPDIF_DATA_FIFO_CSR_TX_ATN_LVL_TU1_WORD_FULL \ - (TEGRA_SPDIF_FIFO_ATN_LVL_D_ONE_SLOT << TEGRA_SPDIF_DATA_FIFO_CSR_TX_ATN_LVL_SHIFT) -#define TEGRA_SPDIF_DATA_FIFO_CSR_TX_ATN_LVL_TU4_WORD_FULL \ - (TEGRA_SPDIF_FIFO_ATN_LVL_D_FOUR_SLOTS << TEGRA_SPDIF_DATA_FIFO_CSR_TX_ATN_LVL_SHIFT) -#define TEGRA_SPDIF_DATA_FIFO_CSR_TX_ATN_LVL_TU8_WORD_FULL \ - (TEGRA_SPDIF_FIFO_ATN_LVL_D_EIGHT_SLOTS << TEGRA_SPDIF_DATA_FIFO_CSR_TX_ATN_LVL_SHIFT) -#define TEGRA_SPDIF_DATA_FIFO_CSR_TX_ATN_LVL_TU12_WORD_FULL \ - (TEGRA_SPDIF_FIFO_ATN_LVL_D_TWELVE_SLOTS << TEGRA_SPDIF_DATA_FIFO_CSR_TX_ATN_LVL_SHIFT) - -/* Number of TX DATA.FIFO levels that could be filled. */ -#define TEGRA_SPDIF_DATA_FIFO_CSR_TX_EMPTY_COUNT_SHIFT 0 -#define TEGRA_SPDIF_DATA_FIFO_CSR_TX_EMPTY_COUNT_MASK (0x1f << SPDIF_DATA_FIFO_CSR_TX_EMPTY_COUNT_SHIFT) - -/* Fields in TEGRA_SPDIF_DATA_OUT */ - -/* - * This register has 5 different formats: - * 16-bit (BIT_MODE=00, PACK=0) - * 20-bit (BIT_MODE=01, PACK=0) - * 24-bit (BIT_MODE=10, PACK=0) - * raw (BIT_MODE=11, PACK=0) - * 16-bit packed (BIT_MODE=00, PACK=1) - */ - -#define TEGRA_SPDIF_DATA_OUT_DATA_16_SHIFT 0 -#define TEGRA_SPDIF_DATA_OUT_DATA_16_MASK (0xffff << TEGRA_SPDIF_DATA_OUT_DATA_16_SHIFT) - -#define TEGRA_SPDIF_DATA_OUT_DATA_20_SHIFT 0 -#define TEGRA_SPDIF_DATA_OUT_DATA_20_MASK (0xfffff << TEGRA_SPDIF_DATA_OUT_DATA_20_SHIFT) - -#define TEGRA_SPDIF_DATA_OUT_DATA_24_SHIFT 0 -#define TEGRA_SPDIF_DATA_OUT_DATA_24_MASK (0xffffff << TEGRA_SPDIF_DATA_OUT_DATA_24_SHIFT) - -#define TEGRA_SPDIF_DATA_OUT_DATA_RAW_P (1 << 31) -#define TEGRA_SPDIF_DATA_OUT_DATA_RAW_C (1 << 30) -#define TEGRA_SPDIF_DATA_OUT_DATA_RAW_U (1 << 29) -#define TEGRA_SPDIF_DATA_OUT_DATA_RAW_V (1 << 28) - -#define TEGRA_SPDIF_DATA_OUT_DATA_RAW_DATA_SHIFT 8 -#define TEGRA_SPDIF_DATA_OUT_DATA_RAW_DATA_MASK (0xfffff << TEGRA_SPDIF_DATA_OUT_DATA_RAW_DATA_SHIFT) - -#define TEGRA_SPDIF_DATA_OUT_DATA_RAW_AUX_SHIFT 4 -#define TEGRA_SPDIF_DATA_OUT_DATA_RAW_AUX_MASK (0xf << TEGRA_SPDIF_DATA_OUT_DATA_RAW_AUX_SHIFT) - -#define TEGRA_SPDIF_DATA_OUT_DATA_RAW_PREAMBLE_SHIFT 0 -#define TEGRA_SPDIF_DATA_OUT_DATA_RAW_PREAMBLE_MASK (0xf << TEGRA_SPDIF_DATA_OUT_DATA_RAW_PREAMBLE_SHIFT) - -#define TEGRA_SPDIF_DATA_OUT_DATA_16_PACKED_RIGHT_SHIFT 16 -#define TEGRA_SPDIF_DATA_OUT_DATA_16_PACKED_RIGHT_MASK (0xffff << TEGRA_SPDIF_DATA_OUT_DATA_16_PACKED_RIGHT_SHIFT) - -#define TEGRA_SPDIF_DATA_OUT_DATA_16_PACKED_LEFT_SHIFT 0 -#define TEGRA_SPDIF_DATA_OUT_DATA_16_PACKED_LEFT_MASK (0xffff << TEGRA_SPDIF_DATA_OUT_DATA_16_PACKED_LEFT_SHIFT) - -/* Fields in TEGRA_SPDIF_DATA_IN */ - -/* - * This register has 5 different formats: - * 16-bit (BIT_MODE=00, PACK=0) - * 20-bit (BIT_MODE=01, PACK=0) - * 24-bit (BIT_MODE=10, PACK=0) - * raw (BIT_MODE=11, PACK=0) - * 16-bit packed (BIT_MODE=00, PACK=1) - * - * Bits 31:24 are common to all modes except 16-bit packed - */ - -#define TEGRA_SPDIF_DATA_IN_DATA_P (1 << 31) -#define TEGRA_SPDIF_DATA_IN_DATA_C (1 << 30) -#define TEGRA_SPDIF_DATA_IN_DATA_U (1 << 29) -#define TEGRA_SPDIF_DATA_IN_DATA_V (1 << 28) - -#define TEGRA_SPDIF_DATA_IN_DATA_PREAMBLE_SHIFT 24 -#define TEGRA_SPDIF_DATA_IN_DATA_PREAMBLE_MASK (0xf << TEGRA_SPDIF_DATA_IN_DATA_PREAMBLE_SHIFT) - -#define TEGRA_SPDIF_DATA_IN_DATA_16_SHIFT 0 -#define TEGRA_SPDIF_DATA_IN_DATA_16_MASK (0xffff << TEGRA_SPDIF_DATA_IN_DATA_16_SHIFT) - -#define TEGRA_SPDIF_DATA_IN_DATA_20_SHIFT 0 -#define TEGRA_SPDIF_DATA_IN_DATA_20_MASK (0xfffff << TEGRA_SPDIF_DATA_IN_DATA_20_SHIFT) - -#define TEGRA_SPDIF_DATA_IN_DATA_24_SHIFT 0 -#define TEGRA_SPDIF_DATA_IN_DATA_24_MASK (0xffffff << TEGRA_SPDIF_DATA_IN_DATA_24_SHIFT) - -#define TEGRA_SPDIF_DATA_IN_DATA_RAW_DATA_SHIFT 8 -#define TEGRA_SPDIF_DATA_IN_DATA_RAW_DATA_MASK (0xfffff << TEGRA_SPDIF_DATA_IN_DATA_RAW_DATA_SHIFT) - -#define TEGRA_SPDIF_DATA_IN_DATA_RAW_AUX_SHIFT 4 -#define TEGRA_SPDIF_DATA_IN_DATA_RAW_AUX_MASK (0xf << TEGRA_SPDIF_DATA_IN_DATA_RAW_AUX_SHIFT) - -#define TEGRA_SPDIF_DATA_IN_DATA_RAW_PREAMBLE_SHIFT 0 -#define TEGRA_SPDIF_DATA_IN_DATA_RAW_PREAMBLE_MASK (0xf << TEGRA_SPDIF_DATA_IN_DATA_RAW_PREAMBLE_SHIFT) - -#define TEGRA_SPDIF_DATA_IN_DATA_16_PACKED_RIGHT_SHIFT 16 -#define TEGRA_SPDIF_DATA_IN_DATA_16_PACKED_RIGHT_MASK (0xffff << TEGRA_SPDIF_DATA_IN_DATA_16_PACKED_RIGHT_SHIFT) - -#define TEGRA_SPDIF_DATA_IN_DATA_16_PACKED_LEFT_SHIFT 0 -#define TEGRA_SPDIF_DATA_IN_DATA_16_PACKED_LEFT_MASK (0xffff << TEGRA_SPDIF_DATA_IN_DATA_16_PACKED_LEFT_SHIFT) - -/* Fields in TEGRA_SPDIF_CH_STA_RX_A */ -/* Fields in TEGRA_SPDIF_CH_STA_RX_B */ -/* Fields in TEGRA_SPDIF_CH_STA_RX_C */ -/* Fields in TEGRA_SPDIF_CH_STA_RX_D */ -/* Fields in TEGRA_SPDIF_CH_STA_RX_E */ -/* Fields in TEGRA_SPDIF_CH_STA_RX_F */ - -/* - * The 6-word receive channel data page buffer holds a block (192 frames) of - * channel status information. The order of receive is from LSB to MSB - * bit, and from CH_STA_RX_A to CH_STA_RX_F then back to CH_STA_RX_A. - */ - -/* Fields in TEGRA_SPDIF_CH_STA_TX_A */ -/* Fields in TEGRA_SPDIF_CH_STA_TX_B */ -/* Fields in TEGRA_SPDIF_CH_STA_TX_C */ -/* Fields in TEGRA_SPDIF_CH_STA_TX_D */ -/* Fields in TEGRA_SPDIF_CH_STA_TX_E */ -/* Fields in TEGRA_SPDIF_CH_STA_TX_F */ - -/* - * The 6-word transmit channel data page buffer holds a block (192 frames) of - * channel status information. The order of transmission is from LSB to MSB - * bit, and from CH_STA_TX_A to CH_STA_TX_F then back to CH_STA_TX_A. - */ - -/* Fields in TEGRA_SPDIF_USR_STA_RX_A */ - -/* - * This 4-word deep FIFO receives user FIFO field information. The order of - * receive is from LSB to MSB bit. - */ - -/* Fields in TEGRA_SPDIF_USR_DAT_TX_A */ - -/* - * This 4-word deep FIFO transmits user FIFO field information. The order of - * transmission is from LSB to MSB bit. - */ - -struct tegra_spdif { - struct clk *clk_spdif_out; - int clk_refs; - struct tegra_pcm_dma_params capture_dma_data; - struct tegra_pcm_dma_params playback_dma_data; - void __iomem *regs; - struct dentry *debug; - u32 reg_ctrl; -}; - -#endif diff --git a/sound/soc/tegra/tegra_wm8753.c b/sound/soc/tegra/tegra_wm8753.c new file mode 100644 index 00000000000..4e77026807a --- /dev/null +++ b/sound/soc/tegra/tegra_wm8753.c @@ -0,0 +1,224 @@ +/* + * tegra_wm8753.c - Tegra machine ASoC driver for boards using WM8753 codec. + * + * Author: Stephen Warren <swarren@nvidia.com> + * Copyright (C) 2010-2012 - NVIDIA, Inc. + * + * Based on code copyright/by: + * + * (c) 2009, 2010 Nvidia Graphics Pvt. Ltd. + * + * Copyright 2007 Wolfson Microelectronics PLC. + * Author: Graeme Gregory + * graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * 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 <asm/mach-types.h> + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/gpio.h> +#include <linux/of_gpio.h> + +#include <sound/core.h> +#include <sound/jack.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> + +#include "../codecs/wm8753.h" + +#include "tegra_asoc_utils.h" + +#define DRV_NAME "tegra-snd-wm8753" + +struct tegra_wm8753 { + struct tegra_asoc_utils_data util_data; +}; + +static int tegra_wm8753_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_codec *codec = rtd->codec; + struct snd_soc_card *card = codec->card; + struct tegra_wm8753 *machine = snd_soc_card_get_drvdata(card); + int srate, mclk; + int err; + + srate = params_rate(params); + switch (srate) { + case 11025: + case 22050: + case 44100: + case 88200: + mclk = 11289600; + break; + default: + mclk = 12288000; + break; + } + + err = tegra_asoc_utils_set_rate(&machine->util_data, srate, mclk); + if (err < 0) { + dev_err(card->dev, "Can't configure clocks\n"); + return err; + } + + err = snd_soc_dai_set_sysclk(codec_dai, WM8753_MCLK, mclk, + SND_SOC_CLOCK_IN); + if (err < 0) { + dev_err(card->dev, "codec_dai clock not set\n"); + return err; + } + + return 0; +} + +static struct snd_soc_ops tegra_wm8753_ops = { + .hw_params = tegra_wm8753_hw_params, +}; + +static const struct snd_soc_dapm_widget tegra_wm8753_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_MIC("Mic Jack", NULL), +}; + +static struct snd_soc_dai_link tegra_wm8753_dai = { + .name = "WM8753", + .stream_name = "WM8753 PCM", + .codec_dai_name = "wm8753-hifi", + .ops = &tegra_wm8753_ops, + .dai_fmt = SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, +}; + +static struct snd_soc_card snd_soc_tegra_wm8753 = { + .name = "tegra-wm8753", + .owner = THIS_MODULE, + .dai_link = &tegra_wm8753_dai, + .num_links = 1, + + .dapm_widgets = tegra_wm8753_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(tegra_wm8753_dapm_widgets), + .fully_routed = true, +}; + +static __devinit int tegra_wm8753_driver_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &snd_soc_tegra_wm8753; + struct tegra_wm8753 *machine; + int ret; + + machine = devm_kzalloc(&pdev->dev, sizeof(struct tegra_wm8753), + GFP_KERNEL); + if (!machine) { + dev_err(&pdev->dev, "Can't allocate tegra_wm8753 struct\n"); + ret = -ENOMEM; + goto err; + } + + card->dev = &pdev->dev; + platform_set_drvdata(pdev, card); + snd_soc_card_set_drvdata(card, machine); + + ret = snd_soc_of_parse_card_name(card, "nvidia,model"); + if (ret) + goto err; + + ret = snd_soc_of_parse_audio_routing(card, "nvidia,audio-routing"); + if (ret) + goto err; + + tegra_wm8753_dai.codec_of_node = of_parse_phandle( + pdev->dev.of_node, "nvidia,audio-codec", 0); + if (!tegra_wm8753_dai.codec_of_node) { + dev_err(&pdev->dev, + "Property 'nvidia,audio-codec' missing or invalid\n"); + ret = -EINVAL; + goto err; + } + + tegra_wm8753_dai.cpu_dai_of_node = of_parse_phandle( + pdev->dev.of_node, "nvidia,i2s-controller", 0); + if (!tegra_wm8753_dai.cpu_dai_of_node) { + dev_err(&pdev->dev, + "Property 'nvidia,i2s-controller' missing or invalid\n"); + ret = -EINVAL; + goto err; + } + + tegra_wm8753_dai.platform_of_node = + tegra_wm8753_dai.cpu_dai_of_node; + + ret = tegra_asoc_utils_init(&machine->util_data, &pdev->dev); + if (ret) + goto err; + + ret = snd_soc_register_card(card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", + ret); + goto err_fini_utils; + } + + return 0; + +err_fini_utils: + tegra_asoc_utils_fini(&machine->util_data); +err: + return ret; +} + +static int __devexit tegra_wm8753_driver_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + struct tegra_wm8753 *machine = snd_soc_card_get_drvdata(card); + + snd_soc_unregister_card(card); + + tegra_asoc_utils_fini(&machine->util_data); + + return 0; +} + +static const struct of_device_id tegra_wm8753_of_match[] __devinitconst = { + { .compatible = "nvidia,tegra-audio-wm8753", }, + {}, +}; + +static struct platform_driver tegra_wm8753_driver = { + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + .pm = &snd_soc_pm_ops, + .of_match_table = tegra_wm8753_of_match, + }, + .probe = tegra_wm8753_driver_probe, + .remove = __devexit_p(tegra_wm8753_driver_remove), +}; +module_platform_driver(tegra_wm8753_driver); + +MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>"); +MODULE_DESCRIPTION("Tegra+WM8753 machine ASoC driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRV_NAME); +MODULE_DEVICE_TABLE(of, tegra_wm8753_of_match); diff --git a/sound/soc/tegra/tegra_wm8903.c b/sound/soc/tegra/tegra_wm8903.c index 566655e23b7..0b0df49d9d3 100644 --- a/sound/soc/tegra/tegra_wm8903.c +++ b/sound/soc/tegra/tegra_wm8903.c @@ -2,7 +2,7 @@ * tegra_wm8903.c - Tegra machine ASoC driver for boards using WM8903 codec. * * Author: Stephen Warren <swarren@nvidia.com> - * Copyright (C) 2010-2011 - NVIDIA, Inc. + * Copyright (C) 2010-2012 - NVIDIA, Inc. * * Based on code copyright/by: * @@ -46,9 +46,6 @@ #include "../codecs/wm8903.h" -#include "tegra_das.h" -#include "tegra_i2s.h" -#include "tegra_pcm.h" #include "tegra_asoc_utils.h" #define DRV_NAME "tegra-snd-wm8903" @@ -61,7 +58,6 @@ struct tegra_wm8903 { struct tegra_wm8903_platform_data pdata; - struct platform_device *pcm_dev; struct tegra_asoc_utils_data util_data; int gpio_requested; }; @@ -354,8 +350,8 @@ static struct snd_soc_dai_link tegra_wm8903_dai = { .name = "WM8903", .stream_name = "WM8903 PCM", .codec_name = "wm8903.0-001a", - .platform_name = "tegra-pcm-audio", - .cpu_dai_name = "tegra-i2s.0", + .platform_name = "tegra20-i2s.0", + .cpu_dai_name = "tegra20-i2s.0", .codec_dai_name = "wm8903-hifi", .init = tegra_wm8903_init, .ops = &tegra_wm8903_ops, @@ -392,7 +388,6 @@ static __devinit int tegra_wm8903_driver_probe(struct platform_device *pdev) ret = -ENOMEM; goto err; } - machine->pcm_dev = ERR_PTR(-EINVAL); card->dev = &pdev->dev; platform_set_drvdata(pdev, card); @@ -428,14 +423,9 @@ static __devinit int tegra_wm8903_driver_probe(struct platform_device *pdev) goto err; } - machine->pcm_dev = platform_device_register_simple( - "tegra-pcm-audio", -1, NULL, 0); - if (IS_ERR(machine->pcm_dev)) { - dev_err(&pdev->dev, - "Can't instantiate tegra-pcm-audio\n"); - ret = PTR_ERR(machine->pcm_dev); - goto err; - } + tegra_wm8903_dai.platform_name = NULL; + tegra_wm8903_dai.platform_of_node = + tegra_wm8903_dai.cpu_dai_of_node; } else { if (machine_is_harmony()) { card->dapm_routes = harmony_audio_map; @@ -454,7 +444,7 @@ static __devinit int tegra_wm8903_driver_probe(struct platform_device *pdev) ret = tegra_asoc_utils_init(&machine->util_data, &pdev->dev); if (ret) - goto err_unregister; + goto err; ret = snd_soc_register_card(card); if (ret) { @@ -467,9 +457,6 @@ static __devinit int tegra_wm8903_driver_probe(struct platform_device *pdev) err_fini_utils: tegra_asoc_utils_fini(&machine->util_data); -err_unregister: - if (!IS_ERR(machine->pcm_dev)) - platform_device_unregister(machine->pcm_dev); err: return ret; } @@ -497,8 +484,6 @@ static int __devexit tegra_wm8903_driver_remove(struct platform_device *pdev) snd_soc_unregister_card(card); tegra_asoc_utils_fini(&machine->util_data); - if (!IS_ERR(machine->pcm_dev)) - platform_device_unregister(machine->pcm_dev); return 0; } diff --git a/sound/soc/tegra/trimslice.c b/sound/soc/tegra/trimslice.c index 2bdfc550cff..4a8d5b672c9 100644 --- a/sound/soc/tegra/trimslice.c +++ b/sound/soc/tegra/trimslice.c @@ -27,6 +27,7 @@ #include <asm/mach-types.h> #include <linux/module.h> +#include <linux/of.h> #include <linux/platform_device.h> #include <linux/slab.h> @@ -38,9 +39,6 @@ #include "../codecs/tlv320aic23.h" -#include "tegra_das.h" -#include "tegra_i2s.h" -#include "tegra_pcm.h" #include "tegra_asoc_utils.h" #define DRV_NAME "tegra-snd-trimslice" @@ -119,8 +117,8 @@ static struct snd_soc_dai_link trimslice_tlv320aic23_dai = { .name = "TLV320AIC23", .stream_name = "AIC23", .codec_name = "tlv320aic23-codec.2-001a", - .platform_name = "tegra-pcm-audio", - .cpu_dai_name = "tegra-i2s.0", + .platform_name = "tegra20-i2s.0", + .cpu_dai_name = "tegra20-i2s.0", .codec_dai_name = "tlv320aic23-hifi", .ops = &trimslice_asoc_ops, }; @@ -152,6 +150,32 @@ static __devinit int tegra_snd_trimslice_probe(struct platform_device *pdev) goto err; } + if (pdev->dev.of_node) { + trimslice_tlv320aic23_dai.codec_name = NULL; + trimslice_tlv320aic23_dai.codec_of_node = of_parse_phandle( + pdev->dev.of_node, "nvidia,audio-codec", 0); + if (!trimslice_tlv320aic23_dai.codec_of_node) { + dev_err(&pdev->dev, + "Property 'nvidia,audio-codec' missing or invalid\n"); + ret = -EINVAL; + goto err; + } + + trimslice_tlv320aic23_dai.cpu_dai_name = NULL; + trimslice_tlv320aic23_dai.cpu_dai_of_node = of_parse_phandle( + pdev->dev.of_node, "nvidia,i2s-controller", 0); + if (!trimslice_tlv320aic23_dai.cpu_dai_of_node) { + dev_err(&pdev->dev, + "Property 'nvidia,i2s-controller' missing or invalid\n"); + ret = -EINVAL; + goto err; + } + + trimslice_tlv320aic23_dai.platform_name = NULL; + trimslice_tlv320aic23_dai.platform_of_node = + trimslice_tlv320aic23_dai.cpu_dai_of_node; + } + ret = tegra_asoc_utils_init(&trimslice->util_data, &pdev->dev); if (ret) goto err; @@ -187,10 +211,17 @@ static int __devexit tegra_snd_trimslice_remove(struct platform_device *pdev) return 0; } +static const struct of_device_id trimslice_of_match[] __devinitconst = { + { .compatible = "nvidia,tegra-audio-trimslice", }, + {}, +}; +MODULE_DEVICE_TABLE(of, trimslice_of_match); + static struct platform_driver tegra_snd_trimslice_driver = { .driver = { .name = DRV_NAME, .owner = THIS_MODULE, + .of_match_table = trimslice_of_match, }, .probe = tegra_snd_trimslice_probe, .remove = __devexit_p(tegra_snd_trimslice_remove), diff --git a/sound/soc/ux500/Kconfig b/sound/soc/ux500/Kconfig new file mode 100644 index 00000000000..44cf43404cd --- /dev/null +++ b/sound/soc/ux500/Kconfig @@ -0,0 +1,14 @@ +# +# Ux500 SoC audio configuration +# +menuconfig SND_SOC_UX500 + tristate "SoC Audio support for Ux500 platform" + depends on SND_SOC + depends on MFD_DB8500_PRCMU + help + Say Y if you want to enable ASoC-support for + any of the Ux500 platforms (e.g. U8500). + +config SND_SOC_UX500_PLAT_MSP_I2S + tristate + depends on SND_SOC_UX500 diff --git a/sound/soc/ux500/Makefile b/sound/soc/ux500/Makefile new file mode 100644 index 00000000000..19974c5a2ea --- /dev/null +++ b/sound/soc/ux500/Makefile @@ -0,0 +1,4 @@ +# Ux500 Platform Support + +snd-soc-ux500-plat-msp-i2s-objs := ux500_msp_dai.o ux500_msp_i2s.o +obj-$(CONFIG_SND_SOC_UX500_PLAT_MSP_I2S) += snd-soc-ux500-plat-msp-i2s.o diff --git a/sound/soc/ux500/ux500_msp_dai.c b/sound/soc/ux500/ux500_msp_dai.c new file mode 100644 index 00000000000..93c6c40e724 --- /dev/null +++ b/sound/soc/ux500/ux500_msp_dai.c @@ -0,0 +1,843 @@ +/* + * Copyright (C) ST-Ericsson SA 2012 + * + * Author: Ola Lilja <ola.o.lilja@stericsson.com>, + * Roger Nilsson <roger.xr.nilsson@stericsson.com> + * for ST-Ericsson. + * + * License terms: + * + * 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/slab.h> +#include <linux/bitops.h> +#include <linux/platform_device.h> +#include <linux/clk.h> +#include <linux/regulator/consumer.h> +#include <linux/mfd/dbx500-prcmu.h> + +#include <mach/hardware.h> +#include <mach/board-mop500-msp.h> + +#include <sound/soc.h> +#include <sound/soc-dai.h> + +#include "ux500_msp_i2s.h" +#include "ux500_msp_dai.h" + +static int setup_pcm_multichan(struct snd_soc_dai *dai, + struct ux500_msp_config *msp_config) +{ + struct ux500_msp_i2s_drvdata *drvdata = dev_get_drvdata(dai->dev); + struct msp_multichannel_config *multi = + &msp_config->multichannel_config; + + if (drvdata->slots > 1) { + msp_config->multichannel_configured = 1; + + multi->tx_multichannel_enable = true; + multi->rx_multichannel_enable = true; + multi->rx_comparison_enable_mode = MSP_COMPARISON_DISABLED; + + multi->tx_channel_0_enable = drvdata->tx_mask; + multi->tx_channel_1_enable = 0; + multi->tx_channel_2_enable = 0; + multi->tx_channel_3_enable = 0; + + multi->rx_channel_0_enable = drvdata->rx_mask; + multi->rx_channel_1_enable = 0; + multi->rx_channel_2_enable = 0; + multi->rx_channel_3_enable = 0; + + dev_dbg(dai->dev, + "%s: Multichannel enabled. Slots: %d, TX: %u, RX: %u\n", + __func__, drvdata->slots, multi->tx_channel_0_enable, + multi->rx_channel_0_enable); + } + + return 0; +} + +static int setup_frameper(struct snd_soc_dai *dai, unsigned int rate, + struct msp_protdesc *prot_desc) +{ + struct ux500_msp_i2s_drvdata *drvdata = dev_get_drvdata(dai->dev); + + switch (drvdata->slots) { + case 1: + switch (rate) { + case 8000: + prot_desc->frame_period = + FRAME_PER_SINGLE_SLOT_8_KHZ; + break; + + case 16000: + prot_desc->frame_period = + FRAME_PER_SINGLE_SLOT_16_KHZ; + break; + + case 44100: + prot_desc->frame_period = + FRAME_PER_SINGLE_SLOT_44_1_KHZ; + break; + + case 48000: + prot_desc->frame_period = + FRAME_PER_SINGLE_SLOT_48_KHZ; + break; + + default: + dev_err(dai->dev, + "%s: Error: Unsupported sample-rate (freq = %d)!\n", + __func__, rate); + return -EINVAL; + } + break; + + case 2: + prot_desc->frame_period = FRAME_PER_2_SLOTS; + break; + + case 8: + prot_desc->frame_period = FRAME_PER_8_SLOTS; + break; + + case 16: + prot_desc->frame_period = FRAME_PER_16_SLOTS; + break; + default: + dev_err(dai->dev, + "%s: Error: Unsupported slot-count (slots = %d)!\n", + __func__, drvdata->slots); + return -EINVAL; + } + + prot_desc->clocks_per_frame = + prot_desc->frame_period+1; + + dev_dbg(dai->dev, "%s: Clocks per frame: %u\n", + __func__, + prot_desc->clocks_per_frame); + + return 0; +} + +static int setup_pcm_framing(struct snd_soc_dai *dai, unsigned int rate, + struct msp_protdesc *prot_desc) +{ + struct ux500_msp_i2s_drvdata *drvdata = dev_get_drvdata(dai->dev); + + u32 frame_length = MSP_FRAME_LEN_1; + prot_desc->frame_width = 0; + + switch (drvdata->slots) { + case 1: + frame_length = MSP_FRAME_LEN_1; + break; + + case 2: + frame_length = MSP_FRAME_LEN_2; + break; + + case 8: + frame_length = MSP_FRAME_LEN_8; + break; + + case 16: + frame_length = MSP_FRAME_LEN_16; + break; + default: + dev_err(dai->dev, + "%s: Error: Unsupported slot-count (slots = %d)!\n", + __func__, drvdata->slots); + return -EINVAL; + } + + prot_desc->tx_frame_len_1 = frame_length; + prot_desc->rx_frame_len_1 = frame_length; + prot_desc->tx_frame_len_2 = frame_length; + prot_desc->rx_frame_len_2 = frame_length; + + prot_desc->tx_elem_len_1 = MSP_ELEM_LEN_16; + prot_desc->rx_elem_len_1 = MSP_ELEM_LEN_16; + prot_desc->tx_elem_len_2 = MSP_ELEM_LEN_16; + prot_desc->rx_elem_len_2 = MSP_ELEM_LEN_16; + + return setup_frameper(dai, rate, prot_desc); +} + +static int setup_clocking(struct snd_soc_dai *dai, + unsigned int fmt, + struct ux500_msp_config *msp_config) +{ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + + case SND_SOC_DAIFMT_NB_IF: + msp_config->tx_fsync_pol ^= 1 << TFSPOL_SHIFT; + msp_config->rx_fsync_pol ^= 1 << RFSPOL_SHIFT; + + break; + + default: + dev_err(dai->dev, + "%s: Error: Unsopported inversion (fmt = 0x%x)!\n", + __func__, fmt); + + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + dev_dbg(dai->dev, "%s: Codec is master.\n", __func__); + + msp_config->iodelay = 0x20; + msp_config->rx_fsync_sel = 0; + msp_config->tx_fsync_sel = 1 << TFSSEL_SHIFT; + msp_config->tx_clk_sel = 0; + msp_config->rx_clk_sel = 0; + msp_config->srg_clk_sel = 0x2 << SCKSEL_SHIFT; + + break; + + case SND_SOC_DAIFMT_CBS_CFS: + dev_dbg(dai->dev, "%s: Codec is slave.\n", __func__); + + msp_config->tx_clk_sel = TX_CLK_SEL_SRG; + msp_config->tx_fsync_sel = TX_SYNC_SRG_PROG; + msp_config->rx_clk_sel = RX_CLK_SEL_SRG; + msp_config->rx_fsync_sel = RX_SYNC_SRG; + msp_config->srg_clk_sel = 1 << SCKSEL_SHIFT; + + break; + + default: + dev_err(dai->dev, "%s: Error: Unsopported master (fmt = 0x%x)!\n", + __func__, fmt); + + return -EINVAL; + } + + return 0; +} + +static int setup_pcm_protdesc(struct snd_soc_dai *dai, + unsigned int fmt, + struct msp_protdesc *prot_desc) +{ + prot_desc->rx_phase_mode = MSP_SINGLE_PHASE; + prot_desc->tx_phase_mode = MSP_SINGLE_PHASE; + prot_desc->rx_phase2_start_mode = MSP_PHASE2_START_MODE_IMEDIATE; + prot_desc->tx_phase2_start_mode = MSP_PHASE2_START_MODE_IMEDIATE; + prot_desc->rx_byte_order = MSP_BTF_MS_BIT_FIRST; + prot_desc->tx_byte_order = MSP_BTF_MS_BIT_FIRST; + prot_desc->tx_fsync_pol = MSP_FSYNC_POL(MSP_FSYNC_POL_ACT_HI); + prot_desc->rx_fsync_pol = MSP_FSYNC_POL_ACT_HI << RFSPOL_SHIFT; + + if ((fmt & SND_SOC_DAIFMT_FORMAT_MASK) == SND_SOC_DAIFMT_DSP_A) { + dev_dbg(dai->dev, "%s: DSP_A.\n", __func__); + prot_desc->rx_clk_pol = MSP_RISING_EDGE; + prot_desc->tx_clk_pol = MSP_FALLING_EDGE; + + prot_desc->rx_data_delay = MSP_DELAY_1; + prot_desc->tx_data_delay = MSP_DELAY_1; + } else { + dev_dbg(dai->dev, "%s: DSP_B.\n", __func__); + prot_desc->rx_clk_pol = MSP_FALLING_EDGE; + prot_desc->tx_clk_pol = MSP_RISING_EDGE; + + prot_desc->rx_data_delay = MSP_DELAY_0; + prot_desc->tx_data_delay = MSP_DELAY_0; + } + + prot_desc->rx_half_word_swap = MSP_SWAP_NONE; + prot_desc->tx_half_word_swap = MSP_SWAP_NONE; + prot_desc->compression_mode = MSP_COMPRESS_MODE_LINEAR; + prot_desc->expansion_mode = MSP_EXPAND_MODE_LINEAR; + prot_desc->frame_sync_ignore = MSP_FSYNC_IGNORE; + + return 0; +} + +static int setup_i2s_protdesc(struct msp_protdesc *prot_desc) +{ + prot_desc->rx_phase_mode = MSP_DUAL_PHASE; + prot_desc->tx_phase_mode = MSP_DUAL_PHASE; + prot_desc->rx_phase2_start_mode = MSP_PHASE2_START_MODE_FSYNC; + prot_desc->tx_phase2_start_mode = MSP_PHASE2_START_MODE_FSYNC; + prot_desc->rx_byte_order = MSP_BTF_MS_BIT_FIRST; + prot_desc->tx_byte_order = MSP_BTF_MS_BIT_FIRST; + prot_desc->tx_fsync_pol = MSP_FSYNC_POL(MSP_FSYNC_POL_ACT_LO); + prot_desc->rx_fsync_pol = MSP_FSYNC_POL_ACT_LO << RFSPOL_SHIFT; + + prot_desc->rx_frame_len_1 = MSP_FRAME_LEN_1; + prot_desc->rx_frame_len_2 = MSP_FRAME_LEN_1; + prot_desc->tx_frame_len_1 = MSP_FRAME_LEN_1; + prot_desc->tx_frame_len_2 = MSP_FRAME_LEN_1; + prot_desc->rx_elem_len_1 = MSP_ELEM_LEN_16; + prot_desc->rx_elem_len_2 = MSP_ELEM_LEN_16; + prot_desc->tx_elem_len_1 = MSP_ELEM_LEN_16; + prot_desc->tx_elem_len_2 = MSP_ELEM_LEN_16; + + prot_desc->rx_clk_pol = MSP_RISING_EDGE; + prot_desc->tx_clk_pol = MSP_FALLING_EDGE; + + prot_desc->rx_data_delay = MSP_DELAY_0; + prot_desc->tx_data_delay = MSP_DELAY_0; + + prot_desc->tx_half_word_swap = MSP_SWAP_NONE; + prot_desc->rx_half_word_swap = MSP_SWAP_NONE; + prot_desc->compression_mode = MSP_COMPRESS_MODE_LINEAR; + prot_desc->expansion_mode = MSP_EXPAND_MODE_LINEAR; + prot_desc->frame_sync_ignore = MSP_FSYNC_IGNORE; + + return 0; +} + +static int setup_msp_config(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai, + struct ux500_msp_config *msp_config) +{ + struct ux500_msp_i2s_drvdata *drvdata = dev_get_drvdata(dai->dev); + struct msp_protdesc *prot_desc = &msp_config->protdesc; + struct snd_pcm_runtime *runtime = substream->runtime; + unsigned int fmt = drvdata->fmt; + int ret; + + memset(msp_config, 0, sizeof(*msp_config)); + + msp_config->f_inputclk = drvdata->master_clk; + + msp_config->tx_fifo_config = TX_FIFO_ENABLE; + msp_config->rx_fifo_config = RX_FIFO_ENABLE; + msp_config->def_elem_len = 1; + msp_config->direction = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? + MSP_DIR_TX : MSP_DIR_RX; + msp_config->data_size = MSP_DATA_BITS_32; + msp_config->frame_freq = runtime->rate; + + dev_dbg(dai->dev, "%s: f_inputclk = %u, frame_freq = %u.\n", + __func__, msp_config->f_inputclk, msp_config->frame_freq); + /* To avoid division by zero */ + prot_desc->clocks_per_frame = 1; + + dev_dbg(dai->dev, "%s: rate: %u, channels: %d.\n", __func__, + runtime->rate, runtime->channels); + switch (fmt & + (SND_SOC_DAIFMT_FORMAT_MASK | SND_SOC_DAIFMT_MASTER_MASK)) { + case SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS: + dev_dbg(dai->dev, "%s: SND_SOC_DAIFMT_I2S.\n", __func__); + + msp_config->default_protdesc = 1; + msp_config->protocol = MSP_I2S_PROTOCOL; + break; + + case SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBM_CFM: + dev_dbg(dai->dev, "%s: SND_SOC_DAIFMT_I2S.\n", __func__); + + msp_config->data_size = MSP_DATA_BITS_16; + msp_config->protocol = MSP_I2S_PROTOCOL; + + ret = setup_i2s_protdesc(prot_desc); + if (ret < 0) + return ret; + + break; + + case SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_CBS_CFS: + case SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_CBM_CFM: + case SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_CBS_CFS: + case SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_CBM_CFM: + dev_dbg(dai->dev, "%s: PCM format.\n", __func__); + + msp_config->data_size = MSP_DATA_BITS_16; + msp_config->protocol = MSP_PCM_PROTOCOL; + + ret = setup_pcm_protdesc(dai, fmt, prot_desc); + if (ret < 0) + return ret; + + ret = setup_pcm_multichan(dai, msp_config); + if (ret < 0) + return ret; + + ret = setup_pcm_framing(dai, runtime->rate, prot_desc); + if (ret < 0) + return ret; + + break; + + default: + dev_err(dai->dev, "%s: Error: Unsopported format (%d)!\n", + __func__, fmt); + return -EINVAL; + } + + return setup_clocking(dai, fmt, msp_config); +} + +static int ux500_msp_dai_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + int ret = 0; + struct ux500_msp_i2s_drvdata *drvdata = dev_get_drvdata(dai->dev); + + dev_dbg(dai->dev, "%s: MSP %d (%s): Enter.\n", __func__, dai->id, + snd_pcm_stream_str(substream)); + + /* Enable regulator */ + ret = regulator_enable(drvdata->reg_vape); + if (ret != 0) { + dev_err(drvdata->msp->dev, + "%s: Failed to enable regulator!\n", __func__); + return ret; + } + + /* Enable clock */ + dev_dbg(dai->dev, "%s: Enabling MSP-clock.\n", __func__); + clk_enable(drvdata->clk); + + return 0; +} + +static void ux500_msp_dai_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + int ret; + struct ux500_msp_i2s_drvdata *drvdata = dev_get_drvdata(dai->dev); + bool is_playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK); + + dev_dbg(dai->dev, "%s: MSP %d (%s): Enter.\n", __func__, dai->id, + snd_pcm_stream_str(substream)); + + if (drvdata->vape_opp_constraint == 1) { + prcmu_qos_update_requirement(PRCMU_QOS_APE_OPP, + "ux500_msp_i2s", 50); + drvdata->vape_opp_constraint = 0; + } + + if (ux500_msp_i2s_close(drvdata->msp, + is_playback ? MSP_DIR_TX : MSP_DIR_RX)) { + dev_err(dai->dev, + "%s: Error: MSP %d (%s): Unable to close i2s.\n", + __func__, dai->id, snd_pcm_stream_str(substream)); + } + + /* Disable clock */ + clk_disable(drvdata->clk); + + /* Disable regulator */ + ret = regulator_disable(drvdata->reg_vape); + if (ret < 0) + dev_err(dai->dev, + "%s: ERROR: Failed to disable regulator (%d)!\n", + __func__, ret); +} + +static int ux500_msp_dai_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + int ret = 0; + struct ux500_msp_i2s_drvdata *drvdata = dev_get_drvdata(dai->dev); + struct snd_pcm_runtime *runtime = substream->runtime; + struct ux500_msp_config msp_config; + + dev_dbg(dai->dev, "%s: MSP %d (%s): Enter (rate = %d).\n", __func__, + dai->id, snd_pcm_stream_str(substream), runtime->rate); + + setup_msp_config(substream, dai, &msp_config); + + ret = ux500_msp_i2s_open(drvdata->msp, &msp_config); + if (ret < 0) { + dev_err(dai->dev, "%s: Error: msp_setup failed (ret = %d)!\n", + __func__, ret); + return ret; + } + + /* Set OPP-level */ + if ((drvdata->fmt & SND_SOC_DAIFMT_MASTER_MASK) && + (drvdata->msp->f_bitclk > 19200000)) { + /* If the bit-clock is higher than 19.2MHz, Vape should be + * run in 100% OPP. Only when bit-clock is used (MSP master) */ + prcmu_qos_update_requirement(PRCMU_QOS_APE_OPP, + "ux500-msp-i2s", 100); + drvdata->vape_opp_constraint = 1; + } else { + prcmu_qos_update_requirement(PRCMU_QOS_APE_OPP, + "ux500-msp-i2s", 50); + drvdata->vape_opp_constraint = 0; + } + + return ret; +} + +static int ux500_msp_dai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + unsigned int mask, slots_active; + struct snd_pcm_runtime *runtime = substream->runtime; + struct ux500_msp_i2s_drvdata *drvdata = dev_get_drvdata(dai->dev); + + dev_dbg(dai->dev, "%s: MSP %d (%s): Enter.\n", + __func__, dai->id, snd_pcm_stream_str(substream)); + + switch (drvdata->fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + snd_pcm_hw_constraint_minmax(runtime, + SNDRV_PCM_HW_PARAM_CHANNELS, + 1, 2); + break; + + case SND_SOC_DAIFMT_DSP_B: + case SND_SOC_DAIFMT_DSP_A: + mask = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? + drvdata->tx_mask : + drvdata->rx_mask; + + slots_active = hweight32(mask); + dev_dbg(dai->dev, "TDM-slots active: %d", slots_active); + + snd_pcm_hw_constraint_minmax(runtime, + SNDRV_PCM_HW_PARAM_CHANNELS, + slots_active, slots_active); + break; + + default: + dev_err(dai->dev, + "%s: Error: Unsupported protocol (fmt = 0x%x)!\n", + __func__, drvdata->fmt); + return -EINVAL; + } + + return 0; +} + +static int ux500_msp_dai_set_dai_fmt(struct snd_soc_dai *dai, + unsigned int fmt) +{ + struct ux500_msp_i2s_drvdata *drvdata = dev_get_drvdata(dai->dev); + + dev_dbg(dai->dev, "%s: MSP %d: Enter.\n", __func__, dai->id); + + switch (fmt & (SND_SOC_DAIFMT_FORMAT_MASK | + SND_SOC_DAIFMT_MASTER_MASK)) { + case SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS: + case SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBM_CFM: + case SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_CBS_CFS: + case SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_CBM_CFM: + case SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_CBS_CFS: + case SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_CBM_CFM: + break; + + default: + dev_err(dai->dev, + "%s: Error: Unsupported protocol/master (fmt = 0x%x)!\n", + __func__, drvdata->fmt); + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + case SND_SOC_DAIFMT_NB_IF: + case SND_SOC_DAIFMT_IB_IF: + break; + + default: + dev_err(dai->dev, + "%s: Error: Unsupported inversion (fmt = 0x%x)!\n", + __func__, drvdata->fmt); + return -EINVAL; + } + + drvdata->fmt = fmt; + return 0; +} + +static int ux500_msp_dai_set_tdm_slot(struct snd_soc_dai *dai, + unsigned int tx_mask, + unsigned int rx_mask, + int slots, int slot_width) +{ + struct ux500_msp_i2s_drvdata *drvdata = dev_get_drvdata(dai->dev); + unsigned int cap; + + switch (slots) { + case 1: + cap = 0x01; + break; + case 2: + cap = 0x03; + break; + case 8: + cap = 0xFF; + break; + case 16: + cap = 0xFFFF; + break; + default: + dev_err(dai->dev, "%s: Error: Unsupported slot-count (%d)!\n", + __func__, slots); + return -EINVAL; + } + drvdata->slots = slots; + + if (!(slot_width == 16)) { + dev_err(dai->dev, "%s: Error: Unsupported slot-width (%d)!\n", + __func__, slot_width); + return -EINVAL; + } + drvdata->slot_width = slot_width; + + drvdata->tx_mask = tx_mask & cap; + drvdata->rx_mask = rx_mask & cap; + + return 0; +} + +static int ux500_msp_dai_set_dai_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + struct ux500_msp_i2s_drvdata *drvdata = dev_get_drvdata(dai->dev); + + dev_dbg(dai->dev, "%s: MSP %d: Enter. clk-id: %d, freq: %u.\n", + __func__, dai->id, clk_id, freq); + + switch (clk_id) { + case UX500_MSP_MASTER_CLOCK: + drvdata->master_clk = freq; + break; + + default: + dev_err(dai->dev, "%s: MSP %d: Invalid clk-id (%d)!\n", + __func__, dai->id, clk_id); + return -EINVAL; + } + + return 0; +} + +static int ux500_msp_dai_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + int ret = 0; + struct ux500_msp_i2s_drvdata *drvdata = dev_get_drvdata(dai->dev); + + dev_dbg(dai->dev, "%s: MSP %d (%s): Enter (msp->id = %d, cmd = %d).\n", + __func__, dai->id, snd_pcm_stream_str(substream), + (int)drvdata->msp->id, cmd); + + ret = ux500_msp_i2s_trigger(drvdata->msp, cmd, substream->stream); + + return ret; +} + +static int ux500_msp_dai_probe(struct snd_soc_dai *dai) +{ + struct ux500_msp_i2s_drvdata *drvdata = dev_get_drvdata(dai->dev); + + drvdata->playback_dma_data.dma_cfg = drvdata->msp->dma_cfg_tx; + drvdata->capture_dma_data.dma_cfg = drvdata->msp->dma_cfg_rx; + + dai->playback_dma_data = &drvdata->playback_dma_data; + dai->capture_dma_data = &drvdata->capture_dma_data; + + drvdata->playback_dma_data.data_size = drvdata->slot_width; + drvdata->capture_dma_data.data_size = drvdata->slot_width; + + return 0; +} + +static struct snd_soc_dai_ops ux500_msp_dai_ops[] = { + { + .set_sysclk = ux500_msp_dai_set_dai_sysclk, + .set_fmt = ux500_msp_dai_set_dai_fmt, + .set_tdm_slot = ux500_msp_dai_set_tdm_slot, + .startup = ux500_msp_dai_startup, + .shutdown = ux500_msp_dai_shutdown, + .prepare = ux500_msp_dai_prepare, + .trigger = ux500_msp_dai_trigger, + .hw_params = ux500_msp_dai_hw_params, + } +}; + +static struct snd_soc_dai_driver ux500_msp_dai_drv[UX500_NBR_OF_DAI] = { + { + .name = "ux500-msp-i2s.0", + .probe = ux500_msp_dai_probe, + .id = 0, + .suspend = NULL, + .resume = NULL, + .playback = { + .channels_min = UX500_MSP_MIN_CHANNELS, + .channels_max = UX500_MSP_MAX_CHANNELS, + .rates = UX500_I2S_RATES, + .formats = UX500_I2S_FORMATS, + }, + .capture = { + .channels_min = UX500_MSP_MIN_CHANNELS, + .channels_max = UX500_MSP_MAX_CHANNELS, + .rates = UX500_I2S_RATES, + .formats = UX500_I2S_FORMATS, + }, + .ops = ux500_msp_dai_ops, + }, + { + .name = "ux500-msp-i2s.1", + .probe = ux500_msp_dai_probe, + .id = 1, + .suspend = NULL, + .resume = NULL, + .playback = { + .channels_min = UX500_MSP_MIN_CHANNELS, + .channels_max = UX500_MSP_MAX_CHANNELS, + .rates = UX500_I2S_RATES, + .formats = UX500_I2S_FORMATS, + }, + .capture = { + .channels_min = UX500_MSP_MIN_CHANNELS, + .channels_max = UX500_MSP_MAX_CHANNELS, + .rates = UX500_I2S_RATES, + .formats = UX500_I2S_FORMATS, + }, + .ops = ux500_msp_dai_ops, + }, + { + .name = "ux500-msp-i2s.2", + .id = 2, + .probe = ux500_msp_dai_probe, + .suspend = NULL, + .resume = NULL, + .playback = { + .channels_min = UX500_MSP_MIN_CHANNELS, + .channels_max = UX500_MSP_MAX_CHANNELS, + .rates = UX500_I2S_RATES, + .formats = UX500_I2S_FORMATS, + }, + .capture = { + .channels_min = UX500_MSP_MIN_CHANNELS, + .channels_max = UX500_MSP_MAX_CHANNELS, + .rates = UX500_I2S_RATES, + .formats = UX500_I2S_FORMATS, + }, + .ops = ux500_msp_dai_ops, + }, + { + .name = "ux500-msp-i2s.3", + .probe = ux500_msp_dai_probe, + .id = 3, + .suspend = NULL, + .resume = NULL, + .playback = { + .channels_min = UX500_MSP_MIN_CHANNELS, + .channels_max = UX500_MSP_MAX_CHANNELS, + .rates = UX500_I2S_RATES, + .formats = UX500_I2S_FORMATS, + }, + .capture = { + .channels_min = UX500_MSP_MIN_CHANNELS, + .channels_max = UX500_MSP_MAX_CHANNELS, + .rates = UX500_I2S_RATES, + .formats = UX500_I2S_FORMATS, + }, + .ops = ux500_msp_dai_ops, + }, +}; + +static int __devinit ux500_msp_drv_probe(struct platform_device *pdev) +{ + struct ux500_msp_i2s_drvdata *drvdata; + int ret = 0; + + dev_dbg(&pdev->dev, "%s: Enter (pdev->name = %s).\n", __func__, + pdev->name); + + drvdata = devm_kzalloc(&pdev->dev, + sizeof(struct ux500_msp_i2s_drvdata), + GFP_KERNEL); + drvdata->fmt = 0; + drvdata->slots = 1; + drvdata->tx_mask = 0x01; + drvdata->rx_mask = 0x01; + drvdata->slot_width = 16; + drvdata->master_clk = MSP_INPUT_FREQ_APB; + + drvdata->reg_vape = devm_regulator_get(&pdev->dev, "v-ape"); + if (IS_ERR(drvdata->reg_vape)) { + ret = (int)PTR_ERR(drvdata->reg_vape); + dev_err(&pdev->dev, + "%s: ERROR: Failed to get Vape supply (%d)!\n", + __func__, ret); + return ret; + } + prcmu_qos_add_requirement(PRCMU_QOS_APE_OPP, (char *)pdev->name, 50); + + drvdata->clk = clk_get(&pdev->dev, NULL); + if (IS_ERR(drvdata->clk)) { + ret = (int)PTR_ERR(drvdata->clk); + dev_err(&pdev->dev, "%s: ERROR: clk_get failed (%d)!\n", + __func__, ret); + goto err_clk; + } + + ret = ux500_msp_i2s_init_msp(pdev, &drvdata->msp, + pdev->dev.platform_data); + if (!drvdata->msp) { + dev_err(&pdev->dev, + "%s: ERROR: Failed to init MSP-struct (%d)!", + __func__, ret); + goto err_init_msp; + } + dev_set_drvdata(&pdev->dev, drvdata); + + ret = snd_soc_register_dai(&pdev->dev, + &ux500_msp_dai_drv[drvdata->msp->id]); + if (ret < 0) { + dev_err(&pdev->dev, "Error: %s: Failed to register MSP%d!\n", + __func__, drvdata->msp->id); + goto err_init_msp; + } + + return 0; + +err_init_msp: + clk_put(drvdata->clk); + +err_clk: + devm_regulator_put(drvdata->reg_vape); + + return ret; +} + +static int __devexit ux500_msp_drv_remove(struct platform_device *pdev) +{ + struct ux500_msp_i2s_drvdata *drvdata = dev_get_drvdata(&pdev->dev); + + snd_soc_unregister_dais(&pdev->dev, ARRAY_SIZE(ux500_msp_dai_drv)); + + devm_regulator_put(drvdata->reg_vape); + prcmu_qos_remove_requirement(PRCMU_QOS_APE_OPP, "ux500_msp_i2s"); + + clk_put(drvdata->clk); + + ux500_msp_i2s_cleanup_msp(pdev, drvdata->msp); + + return 0; +} + +static struct platform_driver msp_i2s_driver = { + .driver = { + .name = "ux500-msp-i2s", + .owner = THIS_MODULE, + }, + .probe = ux500_msp_drv_probe, + .remove = ux500_msp_drv_remove, +}; +module_platform_driver(msp_i2s_driver); + +MODULE_LICENSE("GPLv2"); diff --git a/sound/soc/ux500/ux500_msp_dai.h b/sound/soc/ux500/ux500_msp_dai.h new file mode 100644 index 00000000000..98202a34a5d --- /dev/null +++ b/sound/soc/ux500/ux500_msp_dai.h @@ -0,0 +1,79 @@ +/* + * Copyright (C) ST-Ericsson SA 2012 + * + * Author: Ola Lilja <ola.o.lilja@stericsson.com>, + * Roger Nilsson <roger.xr.nilsson@stericsson.com> + * for ST-Ericsson. + * + * License terms: + * + * 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 UX500_msp_dai_H +#define UX500_msp_dai_H + +#include <linux/types.h> +#include <linux/spinlock.h> + +#include "ux500_msp_i2s.h" + +#define UX500_NBR_OF_DAI 4 + +#define UX500_I2S_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | \ + SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000) + +#define UX500_I2S_FORMATS (SNDRV_PCM_FMTBIT_S16_LE) + +#define FRAME_PER_SINGLE_SLOT_8_KHZ 31 +#define FRAME_PER_SINGLE_SLOT_16_KHZ 124 +#define FRAME_PER_SINGLE_SLOT_44_1_KHZ 63 +#define FRAME_PER_SINGLE_SLOT_48_KHZ 49 +#define FRAME_PER_2_SLOTS 31 +#define FRAME_PER_8_SLOTS 138 +#define FRAME_PER_16_SLOTS 277 + +#ifndef CONFIG_SND_SOC_UX500_AB5500 +#define UX500_MSP_INTERNAL_CLOCK_FREQ 40000000 +#define UX500_MSP1_INTERNAL_CLOCK_FREQ UX500_MSP_INTERNAL_CLOCK_FREQ +#else +#define UX500_MSP_INTERNAL_CLOCK_FREQ 13000000 +#define UX500_MSP1_INTERNAL_CLOCK_FREQ (UX500_MSP_INTERNAL_CLOCK_FREQ * 2) +#endif + +#define UX500_MSP_MIN_CHANNELS 1 +#define UX500_MSP_MAX_CHANNELS 8 + +#define PLAYBACK_CONFIGURED 1 +#define CAPTURE_CONFIGURED 2 + +enum ux500_msp_clock_id { + UX500_MSP_MASTER_CLOCK, +}; + +struct ux500_msp_i2s_drvdata { + struct ux500_msp *msp; + struct regulator *reg_vape; + struct ux500_msp_dma_params playback_dma_data; + struct ux500_msp_dma_params capture_dma_data; + unsigned int fmt; + unsigned int tx_mask; + unsigned int rx_mask; + int slots; + int slot_width; + u8 configured; + int data_delay; + + /* Clocks */ + unsigned int master_clk; + struct clk *clk; + + /* Regulators */ + int vape_opp_constraint; +}; + +int ux500_msp_dai_set_data_delay(struct snd_soc_dai *dai, int delay); + +#endif diff --git a/sound/soc/ux500/ux500_msp_i2s.c b/sound/soc/ux500/ux500_msp_i2s.c new file mode 100644 index 00000000000..496dec10c96 --- /dev/null +++ b/sound/soc/ux500/ux500_msp_i2s.c @@ -0,0 +1,742 @@ +/* + * Copyright (C) ST-Ericsson SA 2012 + * + * Author: Ola Lilja <ola.o.lilja@stericsson.com>, + * Roger Nilsson <roger.xr.nilsson@stericsson.com>, + * Sandeep Kaushik <sandeep.kaushik@st.com> + * for ST-Ericsson. + * + * License terms: + * + * 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/platform_device.h> +#include <linux/delay.h> +#include <linux/slab.h> + +#include <mach/hardware.h> +#include <mach/board-mop500-msp.h> + +#include <sound/soc.h> + +#include "ux500_msp_i2s.h" + + /* Protocol desciptors */ +static const struct msp_protdesc prot_descs[] = { + { /* I2S */ + MSP_SINGLE_PHASE, + MSP_SINGLE_PHASE, + MSP_PHASE2_START_MODE_IMEDIATE, + MSP_PHASE2_START_MODE_IMEDIATE, + MSP_BTF_MS_BIT_FIRST, + MSP_BTF_MS_BIT_FIRST, + MSP_FRAME_LEN_1, + MSP_FRAME_LEN_1, + MSP_FRAME_LEN_1, + MSP_FRAME_LEN_1, + MSP_ELEM_LEN_32, + MSP_ELEM_LEN_32, + MSP_ELEM_LEN_32, + MSP_ELEM_LEN_32, + MSP_DELAY_1, + MSP_DELAY_1, + MSP_RISING_EDGE, + MSP_FALLING_EDGE, + MSP_FSYNC_POL_ACT_LO, + MSP_FSYNC_POL_ACT_LO, + MSP_SWAP_NONE, + MSP_SWAP_NONE, + MSP_COMPRESS_MODE_LINEAR, + MSP_EXPAND_MODE_LINEAR, + MSP_FSYNC_IGNORE, + 31, + 15, + 32, + }, { /* PCM */ + MSP_DUAL_PHASE, + MSP_DUAL_PHASE, + MSP_PHASE2_START_MODE_FSYNC, + MSP_PHASE2_START_MODE_FSYNC, + MSP_BTF_MS_BIT_FIRST, + MSP_BTF_MS_BIT_FIRST, + MSP_FRAME_LEN_1, + MSP_FRAME_LEN_1, + MSP_FRAME_LEN_1, + MSP_FRAME_LEN_1, + MSP_ELEM_LEN_16, + MSP_ELEM_LEN_16, + MSP_ELEM_LEN_16, + MSP_ELEM_LEN_16, + MSP_DELAY_0, + MSP_DELAY_0, + MSP_RISING_EDGE, + MSP_FALLING_EDGE, + MSP_FSYNC_POL_ACT_HI, + MSP_FSYNC_POL_ACT_HI, + MSP_SWAP_NONE, + MSP_SWAP_NONE, + MSP_COMPRESS_MODE_LINEAR, + MSP_EXPAND_MODE_LINEAR, + MSP_FSYNC_IGNORE, + 255, + 0, + 256, + }, { /* Companded PCM */ + MSP_SINGLE_PHASE, + MSP_SINGLE_PHASE, + MSP_PHASE2_START_MODE_FSYNC, + MSP_PHASE2_START_MODE_FSYNC, + MSP_BTF_MS_BIT_FIRST, + MSP_BTF_MS_BIT_FIRST, + MSP_FRAME_LEN_1, + MSP_FRAME_LEN_1, + MSP_FRAME_LEN_1, + MSP_FRAME_LEN_1, + MSP_ELEM_LEN_8, + MSP_ELEM_LEN_8, + MSP_ELEM_LEN_8, + MSP_ELEM_LEN_8, + MSP_DELAY_0, + MSP_DELAY_0, + MSP_RISING_EDGE, + MSP_RISING_EDGE, + MSP_FSYNC_POL_ACT_HI, + MSP_FSYNC_POL_ACT_HI, + MSP_SWAP_NONE, + MSP_SWAP_NONE, + MSP_COMPRESS_MODE_LINEAR, + MSP_EXPAND_MODE_LINEAR, + MSP_FSYNC_IGNORE, + 255, + 0, + 256, + }, +}; + +static void set_prot_desc_tx(struct ux500_msp *msp, + struct msp_protdesc *protdesc, + enum msp_data_size data_size) +{ + u32 temp_reg = 0; + + temp_reg |= MSP_P2_ENABLE_BIT(protdesc->tx_phase_mode); + temp_reg |= MSP_P2_START_MODE_BIT(protdesc->tx_phase2_start_mode); + temp_reg |= MSP_P1_FRAME_LEN_BITS(protdesc->tx_frame_len_1); + temp_reg |= MSP_P2_FRAME_LEN_BITS(protdesc->tx_frame_len_2); + if (msp->def_elem_len) { + temp_reg |= MSP_P1_ELEM_LEN_BITS(protdesc->tx_elem_len_1); + temp_reg |= MSP_P2_ELEM_LEN_BITS(protdesc->tx_elem_len_2); + } else { + temp_reg |= MSP_P1_ELEM_LEN_BITS(data_size); + temp_reg |= MSP_P2_ELEM_LEN_BITS(data_size); + } + temp_reg |= MSP_DATA_DELAY_BITS(protdesc->tx_data_delay); + temp_reg |= MSP_SET_ENDIANNES_BIT(protdesc->tx_byte_order); + temp_reg |= MSP_FSYNC_POL(protdesc->tx_fsync_pol); + temp_reg |= MSP_DATA_WORD_SWAP(protdesc->tx_half_word_swap); + temp_reg |= MSP_SET_COMPANDING_MODE(protdesc->compression_mode); + temp_reg |= MSP_SET_FSYNC_IGNORE(protdesc->frame_sync_ignore); + + writel(temp_reg, msp->registers + MSP_TCF); +} + +static void set_prot_desc_rx(struct ux500_msp *msp, + struct msp_protdesc *protdesc, + enum msp_data_size data_size) +{ + u32 temp_reg = 0; + + temp_reg |= MSP_P2_ENABLE_BIT(protdesc->rx_phase_mode); + temp_reg |= MSP_P2_START_MODE_BIT(protdesc->rx_phase2_start_mode); + temp_reg |= MSP_P1_FRAME_LEN_BITS(protdesc->rx_frame_len_1); + temp_reg |= MSP_P2_FRAME_LEN_BITS(protdesc->rx_frame_len_2); + if (msp->def_elem_len) { + temp_reg |= MSP_P1_ELEM_LEN_BITS(protdesc->rx_elem_len_1); + temp_reg |= MSP_P2_ELEM_LEN_BITS(protdesc->rx_elem_len_2); + } else { + temp_reg |= MSP_P1_ELEM_LEN_BITS(data_size); + temp_reg |= MSP_P2_ELEM_LEN_BITS(data_size); + } + + temp_reg |= MSP_DATA_DELAY_BITS(protdesc->rx_data_delay); + temp_reg |= MSP_SET_ENDIANNES_BIT(protdesc->rx_byte_order); + temp_reg |= MSP_FSYNC_POL(protdesc->rx_fsync_pol); + temp_reg |= MSP_DATA_WORD_SWAP(protdesc->rx_half_word_swap); + temp_reg |= MSP_SET_COMPANDING_MODE(protdesc->expansion_mode); + temp_reg |= MSP_SET_FSYNC_IGNORE(protdesc->frame_sync_ignore); + + writel(temp_reg, msp->registers + MSP_RCF); +} + +static int configure_protocol(struct ux500_msp *msp, + struct ux500_msp_config *config) +{ + struct msp_protdesc *protdesc; + enum msp_data_size data_size; + u32 temp_reg = 0; + + data_size = config->data_size; + msp->def_elem_len = config->def_elem_len; + if (config->default_protdesc == 1) { + if (config->protocol >= MSP_INVALID_PROTOCOL) { + dev_err(msp->dev, "%s: ERROR: Invalid protocol!\n", + __func__); + return -EINVAL; + } + protdesc = + (struct msp_protdesc *)&prot_descs[config->protocol]; + } else { + protdesc = (struct msp_protdesc *)&config->protdesc; + } + + if (data_size < MSP_DATA_BITS_DEFAULT || data_size > MSP_DATA_BITS_32) { + dev_err(msp->dev, + "%s: ERROR: Invalid data-size requested (data_size = %d)!\n", + __func__, data_size); + return -EINVAL; + } + + if (config->direction & MSP_DIR_TX) + set_prot_desc_tx(msp, protdesc, data_size); + if (config->direction & MSP_DIR_RX) + set_prot_desc_rx(msp, protdesc, data_size); + + /* The code below should not be separated. */ + temp_reg = readl(msp->registers + MSP_GCR) & ~TX_CLK_POL_RISING; + temp_reg |= MSP_TX_CLKPOL_BIT(~protdesc->tx_clk_pol); + writel(temp_reg, msp->registers + MSP_GCR); + temp_reg = readl(msp->registers + MSP_GCR) & ~RX_CLK_POL_RISING; + temp_reg |= MSP_RX_CLKPOL_BIT(protdesc->rx_clk_pol); + writel(temp_reg, msp->registers + MSP_GCR); + + return 0; +} + +static int setup_bitclk(struct ux500_msp *msp, struct ux500_msp_config *config) +{ + u32 reg_val_GCR; + u32 frame_per = 0; + u32 sck_div = 0; + u32 frame_width = 0; + u32 temp_reg = 0; + struct msp_protdesc *protdesc = NULL; + + reg_val_GCR = readl(msp->registers + MSP_GCR); + writel(reg_val_GCR & ~SRG_ENABLE, msp->registers + MSP_GCR); + + if (config->default_protdesc) + protdesc = + (struct msp_protdesc *)&prot_descs[config->protocol]; + else + protdesc = (struct msp_protdesc *)&config->protdesc; + + switch (config->protocol) { + case MSP_PCM_PROTOCOL: + case MSP_PCM_COMPAND_PROTOCOL: + frame_width = protdesc->frame_width; + sck_div = config->f_inputclk / (config->frame_freq * + (protdesc->clocks_per_frame)); + frame_per = protdesc->frame_period; + break; + case MSP_I2S_PROTOCOL: + frame_width = protdesc->frame_width; + sck_div = config->f_inputclk / (config->frame_freq * + (protdesc->clocks_per_frame)); + frame_per = protdesc->frame_period; + break; + default: + dev_err(msp->dev, "%s: ERROR: Unknown protocol (%d)!\n", + __func__, + config->protocol); + return -EINVAL; + } + + temp_reg = (sck_div - 1) & SCK_DIV_MASK; + temp_reg |= FRAME_WIDTH_BITS(frame_width); + temp_reg |= FRAME_PERIOD_BITS(frame_per); + writel(temp_reg, msp->registers + MSP_SRG); + + msp->f_bitclk = (config->f_inputclk)/(sck_div + 1); + + /* Enable bit-clock */ + udelay(100); + reg_val_GCR = readl(msp->registers + MSP_GCR); + writel(reg_val_GCR | SRG_ENABLE, msp->registers + MSP_GCR); + udelay(100); + + return 0; +} + +static int configure_multichannel(struct ux500_msp *msp, + struct ux500_msp_config *config) +{ + struct msp_protdesc *protdesc; + struct msp_multichannel_config *mcfg; + u32 reg_val_MCR; + + if (config->default_protdesc == 1) { + if (config->protocol >= MSP_INVALID_PROTOCOL) { + dev_err(msp->dev, + "%s: ERROR: Invalid protocol (%d)!\n", + __func__, config->protocol); + return -EINVAL; + } + protdesc = (struct msp_protdesc *) + &prot_descs[config->protocol]; + } else { + protdesc = (struct msp_protdesc *)&config->protdesc; + } + + mcfg = &config->multichannel_config; + if (mcfg->tx_multichannel_enable) { + if (protdesc->tx_phase_mode == MSP_SINGLE_PHASE) { + reg_val_MCR = readl(msp->registers + MSP_MCR); + writel(reg_val_MCR | (mcfg->tx_multichannel_enable ? + 1 << TMCEN_BIT : 0), + msp->registers + MSP_MCR); + writel(mcfg->tx_channel_0_enable, + msp->registers + MSP_TCE0); + writel(mcfg->tx_channel_1_enable, + msp->registers + MSP_TCE1); + writel(mcfg->tx_channel_2_enable, + msp->registers + MSP_TCE2); + writel(mcfg->tx_channel_3_enable, + msp->registers + MSP_TCE3); + } else { + dev_err(msp->dev, + "%s: ERROR: Only single-phase supported (TX-mode: %d)!\n", + __func__, protdesc->tx_phase_mode); + return -EINVAL; + } + } + if (mcfg->rx_multichannel_enable) { + if (protdesc->rx_phase_mode == MSP_SINGLE_PHASE) { + reg_val_MCR = readl(msp->registers + MSP_MCR); + writel(reg_val_MCR | (mcfg->rx_multichannel_enable ? + 1 << RMCEN_BIT : 0), + msp->registers + MSP_MCR); + writel(mcfg->rx_channel_0_enable, + msp->registers + MSP_RCE0); + writel(mcfg->rx_channel_1_enable, + msp->registers + MSP_RCE1); + writel(mcfg->rx_channel_2_enable, + msp->registers + MSP_RCE2); + writel(mcfg->rx_channel_3_enable, + msp->registers + MSP_RCE3); + } else { + dev_err(msp->dev, + "%s: ERROR: Only single-phase supported (RX-mode: %d)!\n", + __func__, protdesc->rx_phase_mode); + return -EINVAL; + } + if (mcfg->rx_comparison_enable_mode) { + reg_val_MCR = readl(msp->registers + MSP_MCR); + writel(reg_val_MCR | + (mcfg->rx_comparison_enable_mode << RCMPM_BIT), + msp->registers + MSP_MCR); + + writel(mcfg->comparison_mask, + msp->registers + MSP_RCM); + writel(mcfg->comparison_value, + msp->registers + MSP_RCV); + + } + } + + return 0; +} + +static int enable_msp(struct ux500_msp *msp, struct ux500_msp_config *config) +{ + int status = 0; + u32 reg_val_DMACR, reg_val_GCR; + + /* Check msp state whether in RUN or CONFIGURED Mode */ + if ((msp->msp_state == MSP_STATE_IDLE) && (msp->plat_init)) { + status = msp->plat_init(); + if (status) { + dev_err(msp->dev, "%s: ERROR: Failed to init MSP (%d)!\n", + __func__, status); + return status; + } + } + + /* Configure msp with protocol dependent settings */ + configure_protocol(msp, config); + setup_bitclk(msp, config); + if (config->multichannel_configured == 1) { + status = configure_multichannel(msp, config); + if (status) + dev_warn(msp->dev, + "%s: WARN: configure_multichannel failed (%d)!\n", + __func__, status); + } + + /* Make sure the correct DMA-directions are configured */ + if ((config->direction & MSP_DIR_RX) && (!msp->dma_cfg_rx)) { + dev_err(msp->dev, "%s: ERROR: MSP RX-mode is not configured!", + __func__); + return -EINVAL; + } + if ((config->direction == MSP_DIR_TX) && (!msp->dma_cfg_tx)) { + dev_err(msp->dev, "%s: ERROR: MSP TX-mode is not configured!", + __func__); + return -EINVAL; + } + + reg_val_DMACR = readl(msp->registers + MSP_DMACR); + if (config->direction & MSP_DIR_RX) + reg_val_DMACR |= RX_DMA_ENABLE; + if (config->direction & MSP_DIR_TX) + reg_val_DMACR |= TX_DMA_ENABLE; + writel(reg_val_DMACR, msp->registers + MSP_DMACR); + + writel(config->iodelay, msp->registers + MSP_IODLY); + + /* Enable frame generation logic */ + reg_val_GCR = readl(msp->registers + MSP_GCR); + writel(reg_val_GCR | FRAME_GEN_ENABLE, msp->registers + MSP_GCR); + + return status; +} + +static void flush_fifo_rx(struct ux500_msp *msp) +{ + u32 reg_val_DR, reg_val_GCR, reg_val_FLR; + u32 limit = 32; + + reg_val_GCR = readl(msp->registers + MSP_GCR); + writel(reg_val_GCR | RX_ENABLE, msp->registers + MSP_GCR); + + reg_val_FLR = readl(msp->registers + MSP_FLR); + while (!(reg_val_FLR & RX_FIFO_EMPTY) && limit--) { + reg_val_DR = readl(msp->registers + MSP_DR); + reg_val_FLR = readl(msp->registers + MSP_FLR); + } + + writel(reg_val_GCR, msp->registers + MSP_GCR); +} + +static void flush_fifo_tx(struct ux500_msp *msp) +{ + u32 reg_val_TSTDR, reg_val_GCR, reg_val_FLR; + u32 limit = 32; + + reg_val_GCR = readl(msp->registers + MSP_GCR); + writel(reg_val_GCR | TX_ENABLE, msp->registers + MSP_GCR); + writel(MSP_ITCR_ITEN | MSP_ITCR_TESTFIFO, msp->registers + MSP_ITCR); + + reg_val_FLR = readl(msp->registers + MSP_FLR); + while (!(reg_val_FLR & TX_FIFO_EMPTY) && limit--) { + reg_val_TSTDR = readl(msp->registers + MSP_TSTDR); + reg_val_FLR = readl(msp->registers + MSP_FLR); + } + writel(0x0, msp->registers + MSP_ITCR); + writel(reg_val_GCR, msp->registers + MSP_GCR); +} + +int ux500_msp_i2s_open(struct ux500_msp *msp, + struct ux500_msp_config *config) +{ + u32 old_reg, new_reg, mask; + int res; + unsigned int tx_sel, rx_sel, tx_busy, rx_busy; + + if (in_interrupt()) { + dev_err(msp->dev, + "%s: ERROR: Open called in interrupt context!\n", + __func__); + return -1; + } + + tx_sel = (config->direction & MSP_DIR_TX) > 0; + rx_sel = (config->direction & MSP_DIR_RX) > 0; + if (!tx_sel && !rx_sel) { + dev_err(msp->dev, "%s: Error: No direction selected!\n", + __func__); + return -EINVAL; + } + + tx_busy = (msp->dir_busy & MSP_DIR_TX) > 0; + rx_busy = (msp->dir_busy & MSP_DIR_RX) > 0; + if (tx_busy && tx_sel) { + dev_err(msp->dev, "%s: Error: TX is in use!\n", __func__); + return -EBUSY; + } + if (rx_busy && rx_sel) { + dev_err(msp->dev, "%s: Error: RX is in use!\n", __func__); + return -EBUSY; + } + + msp->dir_busy |= (tx_sel ? MSP_DIR_TX : 0) | (rx_sel ? MSP_DIR_RX : 0); + + /* First do the global config register */ + mask = RX_CLK_SEL_MASK | TX_CLK_SEL_MASK | RX_FSYNC_MASK | + TX_FSYNC_MASK | RX_SYNC_SEL_MASK | TX_SYNC_SEL_MASK | + RX_FIFO_ENABLE_MASK | TX_FIFO_ENABLE_MASK | SRG_CLK_SEL_MASK | + LOOPBACK_MASK | TX_EXTRA_DELAY_MASK; + + new_reg = (config->tx_clk_sel | config->rx_clk_sel | + config->rx_fsync_pol | config->tx_fsync_pol | + config->rx_fsync_sel | config->tx_fsync_sel | + config->rx_fifo_config | config->tx_fifo_config | + config->srg_clk_sel | config->loopback_enable | + config->tx_data_enable); + + old_reg = readl(msp->registers + MSP_GCR); + old_reg &= ~mask; + new_reg |= old_reg; + writel(new_reg, msp->registers + MSP_GCR); + + res = enable_msp(msp, config); + if (res < 0) { + dev_err(msp->dev, "%s: ERROR: enable_msp failed (%d)!\n", + __func__, res); + return -EBUSY; + } + if (config->loopback_enable & 0x80) + msp->loopback_enable = 1; + + /* Flush FIFOs */ + flush_fifo_tx(msp); + flush_fifo_rx(msp); + + msp->msp_state = MSP_STATE_CONFIGURED; + return 0; +} + +static void disable_msp_rx(struct ux500_msp *msp) +{ + u32 reg_val_GCR, reg_val_DMACR, reg_val_IMSC; + + reg_val_GCR = readl(msp->registers + MSP_GCR); + writel(reg_val_GCR & ~RX_ENABLE, msp->registers + MSP_GCR); + reg_val_DMACR = readl(msp->registers + MSP_DMACR); + writel(reg_val_DMACR & ~RX_DMA_ENABLE, msp->registers + MSP_DMACR); + reg_val_IMSC = readl(msp->registers + MSP_IMSC); + writel(reg_val_IMSC & + ~(RX_SERVICE_INT | RX_OVERRUN_ERROR_INT), + msp->registers + MSP_IMSC); + + msp->dir_busy &= ~MSP_DIR_RX; +} + +static void disable_msp_tx(struct ux500_msp *msp) +{ + u32 reg_val_GCR, reg_val_DMACR, reg_val_IMSC; + + reg_val_GCR = readl(msp->registers + MSP_GCR); + writel(reg_val_GCR & ~TX_ENABLE, msp->registers + MSP_GCR); + reg_val_DMACR = readl(msp->registers + MSP_DMACR); + writel(reg_val_DMACR & ~TX_DMA_ENABLE, msp->registers + MSP_DMACR); + reg_val_IMSC = readl(msp->registers + MSP_IMSC); + writel(reg_val_IMSC & + ~(TX_SERVICE_INT | TX_UNDERRUN_ERR_INT), + msp->registers + MSP_IMSC); + + msp->dir_busy &= ~MSP_DIR_TX; +} + +static int disable_msp(struct ux500_msp *msp, unsigned int dir) +{ + u32 reg_val_GCR; + int status = 0; + unsigned int disable_tx, disable_rx; + + reg_val_GCR = readl(msp->registers + MSP_GCR); + disable_tx = dir & MSP_DIR_TX; + disable_rx = dir & MSP_DIR_TX; + if (disable_tx && disable_rx) { + reg_val_GCR = readl(msp->registers + MSP_GCR); + writel(reg_val_GCR | LOOPBACK_MASK, + msp->registers + MSP_GCR); + + /* Flush TX-FIFO */ + flush_fifo_tx(msp); + + /* Disable TX-channel */ + writel((readl(msp->registers + MSP_GCR) & + (~TX_ENABLE)), msp->registers + MSP_GCR); + + /* Flush RX-FIFO */ + flush_fifo_rx(msp); + + /* Disable Loopback and Receive channel */ + writel((readl(msp->registers + MSP_GCR) & + (~(RX_ENABLE | LOOPBACK_MASK))), + msp->registers + MSP_GCR); + + disable_msp_tx(msp); + disable_msp_rx(msp); + } else if (disable_tx) + disable_msp_tx(msp); + else if (disable_rx) + disable_msp_rx(msp); + + return status; +} + +int ux500_msp_i2s_trigger(struct ux500_msp *msp, int cmd, int direction) +{ + u32 reg_val_GCR, enable_bit; + + if (msp->msp_state == MSP_STATE_IDLE) { + dev_err(msp->dev, "%s: ERROR: MSP is not configured!\n", + __func__); + return -EINVAL; + } + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (direction == SNDRV_PCM_STREAM_PLAYBACK) + enable_bit = TX_ENABLE; + else + enable_bit = RX_ENABLE; + reg_val_GCR = readl(msp->registers + MSP_GCR); + writel(reg_val_GCR | enable_bit, msp->registers + MSP_GCR); + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (direction == SNDRV_PCM_STREAM_PLAYBACK) + disable_msp_tx(msp); + else + disable_msp_rx(msp); + break; + default: + return -EINVAL; + break; + } + + return 0; +} + +int ux500_msp_i2s_close(struct ux500_msp *msp, unsigned int dir) +{ + int status = 0; + + dev_dbg(msp->dev, "%s: Enter (dir = 0x%01x).\n", __func__, dir); + + status = disable_msp(msp, dir); + if (msp->dir_busy == 0) { + /* disable sample rate and frame generators */ + msp->msp_state = MSP_STATE_IDLE; + writel((readl(msp->registers + MSP_GCR) & + (~(FRAME_GEN_ENABLE | SRG_ENABLE))), + msp->registers + MSP_GCR); + if (msp->plat_exit) + status = msp->plat_exit(); + if (status) + dev_warn(msp->dev, + "%s: WARN: ux500_msp_i2s_exit failed (%d)!\n", + __func__, status); + writel(0, msp->registers + MSP_GCR); + writel(0, msp->registers + MSP_TCF); + writel(0, msp->registers + MSP_RCF); + writel(0, msp->registers + MSP_DMACR); + writel(0, msp->registers + MSP_SRG); + writel(0, msp->registers + MSP_MCR); + writel(0, msp->registers + MSP_RCM); + writel(0, msp->registers + MSP_RCV); + writel(0, msp->registers + MSP_TCE0); + writel(0, msp->registers + MSP_TCE1); + writel(0, msp->registers + MSP_TCE2); + writel(0, msp->registers + MSP_TCE3); + writel(0, msp->registers + MSP_RCE0); + writel(0, msp->registers + MSP_RCE1); + writel(0, msp->registers + MSP_RCE2); + writel(0, msp->registers + MSP_RCE3); + } + + return status; + +} + +int ux500_msp_i2s_init_msp(struct platform_device *pdev, + struct ux500_msp **msp_p, + struct msp_i2s_platform_data *platform_data) +{ + int ret = 0; + struct resource *res = NULL; + struct i2s_controller *i2s_cont; + struct ux500_msp *msp; + + dev_dbg(&pdev->dev, "%s: Enter (name: %s, id: %d).\n", __func__, + pdev->name, platform_data->id); + + *msp_p = devm_kzalloc(&pdev->dev, sizeof(struct ux500_msp), GFP_KERNEL); + msp = *msp_p; + + msp->id = platform_data->id; + msp->dev = &pdev->dev; + msp->plat_init = platform_data->msp_i2s_init; + msp->plat_exit = platform_data->msp_i2s_exit; + msp->dma_cfg_rx = platform_data->msp_i2s_dma_rx; + msp->dma_cfg_tx = platform_data->msp_i2s_dma_tx; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res == NULL) { + dev_err(&pdev->dev, "%s: ERROR: Unable to get resource!\n", + __func__); + ret = -ENOMEM; + goto err_res; + } + + msp->registers = ioremap(res->start, (res->end - res->start + 1)); + if (msp->registers == NULL) { + dev_err(&pdev->dev, "%s: ERROR: ioremap failed!\n", __func__); + ret = -ENOMEM; + goto err_res; + } + + msp->msp_state = MSP_STATE_IDLE; + msp->loopback_enable = 0; + + /* I2S-controller is allocated and added in I2S controller class. */ + i2s_cont = devm_kzalloc(&pdev->dev, sizeof(*i2s_cont), GFP_KERNEL); + if (!i2s_cont) { + dev_err(&pdev->dev, + "%s: ERROR: Failed to allocate I2S-controller!\n", + __func__); + goto err_i2s_cont; + } + i2s_cont->dev.parent = &pdev->dev; + i2s_cont->data = (void *)msp; + i2s_cont->id = (s16)msp->id; + snprintf(i2s_cont->name, sizeof(i2s_cont->name), "ux500-msp-i2s.%04x", + msp->id); + dev_dbg(&pdev->dev, "I2S device-name: '%s'\n", i2s_cont->name); + msp->i2s_cont = i2s_cont; + + return 0; + +err_i2s_cont: + iounmap(msp->registers); + +err_res: + devm_kfree(&pdev->dev, msp); + + return ret; +} + +void ux500_msp_i2s_cleanup_msp(struct platform_device *pdev, + struct ux500_msp *msp) +{ + dev_dbg(msp->dev, "%s: Enter (id = %d).\n", __func__, msp->id); + + device_unregister(&msp->i2s_cont->dev); + devm_kfree(&pdev->dev, msp->i2s_cont); + + iounmap(msp->registers); + + devm_kfree(&pdev->dev, msp); +} + +MODULE_LICENSE("GPLv2"); diff --git a/sound/soc/ux500/ux500_msp_i2s.h b/sound/soc/ux500/ux500_msp_i2s.h new file mode 100644 index 00000000000..7f71b4a0d4b --- /dev/null +++ b/sound/soc/ux500/ux500_msp_i2s.h @@ -0,0 +1,553 @@ +/* + * Copyright (C) ST-Ericsson SA 2012 + * + * Author: Ola Lilja <ola.o.lilja@stericsson.com>, + * for ST-Ericsson. + * + * License terms: + * + * 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 UX500_MSP_I2S_H +#define UX500_MSP_I2S_H + +#include <linux/platform_device.h> + +#include <mach/board-mop500-msp.h> + +#define MSP_INPUT_FREQ_APB 48000000 + +/*** Stereo mode. Used for APB data accesses as 16 bits accesses (mono), + * 32 bits accesses (stereo). + ***/ +enum msp_stereo_mode { + MSP_MONO, + MSP_STEREO +}; + +/* Direction (Transmit/Receive mode) */ +enum msp_direction { + MSP_TX = 1, + MSP_RX = 2 +}; + +/* Transmit and receive configuration register */ +#define MSP_BIG_ENDIAN 0x00000000 +#define MSP_LITTLE_ENDIAN 0x00001000 +#define MSP_UNEXPECTED_FS_ABORT 0x00000000 +#define MSP_UNEXPECTED_FS_IGNORE 0x00008000 +#define MSP_NON_MODE_BIT_MASK 0x00009000 + +/* Global configuration register */ +#define RX_ENABLE 0x00000001 +#define RX_FIFO_ENABLE 0x00000002 +#define RX_SYNC_SRG 0x00000010 +#define RX_CLK_POL_RISING 0x00000020 +#define RX_CLK_SEL_SRG 0x00000040 +#define TX_ENABLE 0x00000100 +#define TX_FIFO_ENABLE 0x00000200 +#define TX_SYNC_SRG_PROG 0x00001800 +#define TX_SYNC_SRG_AUTO 0x00001000 +#define TX_CLK_POL_RISING 0x00002000 +#define TX_CLK_SEL_SRG 0x00004000 +#define TX_EXTRA_DELAY_ENABLE 0x00008000 +#define SRG_ENABLE 0x00010000 +#define FRAME_GEN_ENABLE 0x00100000 +#define SRG_CLK_SEL_APB 0x00000000 +#define RX_FIFO_SYNC_HI 0x00000000 +#define TX_FIFO_SYNC_HI 0x00000000 +#define SPI_CLK_MODE_NORMAL 0x00000000 + +#define MSP_FRAME_SIZE_AUTO -1 + +#define MSP_DR 0x00 +#define MSP_GCR 0x04 +#define MSP_TCF 0x08 +#define MSP_RCF 0x0c +#define MSP_SRG 0x10 +#define MSP_FLR 0x14 +#define MSP_DMACR 0x18 + +#define MSP_IMSC 0x20 +#define MSP_RIS 0x24 +#define MSP_MIS 0x28 +#define MSP_ICR 0x2c +#define MSP_MCR 0x30 +#define MSP_RCV 0x34 +#define MSP_RCM 0x38 + +#define MSP_TCE0 0x40 +#define MSP_TCE1 0x44 +#define MSP_TCE2 0x48 +#define MSP_TCE3 0x4c + +#define MSP_RCE0 0x60 +#define MSP_RCE1 0x64 +#define MSP_RCE2 0x68 +#define MSP_RCE3 0x6c +#define MSP_IODLY 0x70 + +#define MSP_ITCR 0x80 +#define MSP_ITIP 0x84 +#define MSP_ITOP 0x88 +#define MSP_TSTDR 0x8c + +#define MSP_PID0 0xfe0 +#define MSP_PID1 0xfe4 +#define MSP_PID2 0xfe8 +#define MSP_PID3 0xfec + +#define MSP_CID0 0xff0 +#define MSP_CID1 0xff4 +#define MSP_CID2 0xff8 +#define MSP_CID3 0xffc + +/* Protocol dependant parameters list */ +#define RX_ENABLE_MASK BIT(0) +#define RX_FIFO_ENABLE_MASK BIT(1) +#define RX_FSYNC_MASK BIT(2) +#define DIRECT_COMPANDING_MASK BIT(3) +#define RX_SYNC_SEL_MASK BIT(4) +#define RX_CLK_POL_MASK BIT(5) +#define RX_CLK_SEL_MASK BIT(6) +#define LOOPBACK_MASK BIT(7) +#define TX_ENABLE_MASK BIT(8) +#define TX_FIFO_ENABLE_MASK BIT(9) +#define TX_FSYNC_MASK BIT(10) +#define TX_MSP_TDR_TSR BIT(11) +#define TX_SYNC_SEL_MASK (BIT(12) | BIT(11)) +#define TX_CLK_POL_MASK BIT(13) +#define TX_CLK_SEL_MASK BIT(14) +#define TX_EXTRA_DELAY_MASK BIT(15) +#define SRG_ENABLE_MASK BIT(16) +#define SRG_CLK_POL_MASK BIT(17) +#define SRG_CLK_SEL_MASK (BIT(19) | BIT(18)) +#define FRAME_GEN_EN_MASK BIT(20) +#define SPI_CLK_MODE_MASK (BIT(22) | BIT(21)) +#define SPI_BURST_MODE_MASK BIT(23) + +#define RXEN_SHIFT 0 +#define RFFEN_SHIFT 1 +#define RFSPOL_SHIFT 2 +#define DCM_SHIFT 3 +#define RFSSEL_SHIFT 4 +#define RCKPOL_SHIFT 5 +#define RCKSEL_SHIFT 6 +#define LBM_SHIFT 7 +#define TXEN_SHIFT 8 +#define TFFEN_SHIFT 9 +#define TFSPOL_SHIFT 10 +#define TFSSEL_SHIFT 11 +#define TCKPOL_SHIFT 13 +#define TCKSEL_SHIFT 14 +#define TXDDL_SHIFT 15 +#define SGEN_SHIFT 16 +#define SCKPOL_SHIFT 17 +#define SCKSEL_SHIFT 18 +#define FGEN_SHIFT 20 +#define SPICKM_SHIFT 21 +#define TBSWAP_SHIFT 28 + +#define RCKPOL_MASK BIT(0) +#define TCKPOL_MASK BIT(0) +#define SPICKM_MASK (BIT(1) | BIT(0)) +#define MSP_RX_CLKPOL_BIT(n) ((n & RCKPOL_MASK) << RCKPOL_SHIFT) +#define MSP_TX_CLKPOL_BIT(n) ((n & TCKPOL_MASK) << TCKPOL_SHIFT) + +#define P1ELEN_SHIFT 0 +#define P1FLEN_SHIFT 3 +#define DTYP_SHIFT 10 +#define ENDN_SHIFT 12 +#define DDLY_SHIFT 13 +#define FSIG_SHIFT 15 +#define P2ELEN_SHIFT 16 +#define P2FLEN_SHIFT 19 +#define P2SM_SHIFT 26 +#define P2EN_SHIFT 27 +#define FSYNC_SHIFT 15 + +#define P1ELEN_MASK 0x00000007 +#define P2ELEN_MASK 0x00070000 +#define P1FLEN_MASK 0x00000378 +#define P2FLEN_MASK 0x03780000 +#define DDLY_MASK 0x00003000 +#define DTYP_MASK 0x00000600 +#define P2SM_MASK 0x04000000 +#define P2EN_MASK 0x08000000 +#define ENDN_MASK 0x00001000 +#define TFSPOL_MASK 0x00000400 +#define TBSWAP_MASK 0x30000000 +#define COMPANDING_MODE_MASK 0x00000c00 +#define FSYNC_MASK 0x00008000 + +#define MSP_P1_ELEM_LEN_BITS(n) (n & P1ELEN_MASK) +#define MSP_P2_ELEM_LEN_BITS(n) (((n) << P2ELEN_SHIFT) & P2ELEN_MASK) +#define MSP_P1_FRAME_LEN_BITS(n) (((n) << P1FLEN_SHIFT) & P1FLEN_MASK) +#define MSP_P2_FRAME_LEN_BITS(n) (((n) << P2FLEN_SHIFT) & P2FLEN_MASK) +#define MSP_DATA_DELAY_BITS(n) (((n) << DDLY_SHIFT) & DDLY_MASK) +#define MSP_DATA_TYPE_BITS(n) (((n) << DTYP_SHIFT) & DTYP_MASK) +#define MSP_P2_START_MODE_BIT(n) ((n << P2SM_SHIFT) & P2SM_MASK) +#define MSP_P2_ENABLE_BIT(n) ((n << P2EN_SHIFT) & P2EN_MASK) +#define MSP_SET_ENDIANNES_BIT(n) ((n << ENDN_SHIFT) & ENDN_MASK) +#define MSP_FSYNC_POL(n) ((n << TFSPOL_SHIFT) & TFSPOL_MASK) +#define MSP_DATA_WORD_SWAP(n) ((n << TBSWAP_SHIFT) & TBSWAP_MASK) +#define MSP_SET_COMPANDING_MODE(n) ((n << DTYP_SHIFT) & \ + COMPANDING_MODE_MASK) +#define MSP_SET_FSYNC_IGNORE(n) ((n << FSYNC_SHIFT) & FSYNC_MASK) + +/* Flag register */ +#define RX_BUSY BIT(0) +#define RX_FIFO_EMPTY BIT(1) +#define RX_FIFO_FULL BIT(2) +#define TX_BUSY BIT(3) +#define TX_FIFO_EMPTY BIT(4) +#define TX_FIFO_FULL BIT(5) + +#define RBUSY_SHIFT 0 +#define RFE_SHIFT 1 +#define RFU_SHIFT 2 +#define TBUSY_SHIFT 3 +#define TFE_SHIFT 4 +#define TFU_SHIFT 5 + +/* Multichannel control register */ +#define RMCEN_SHIFT 0 +#define RMCSF_SHIFT 1 +#define RCMPM_SHIFT 3 +#define TMCEN_SHIFT 5 +#define TNCSF_SHIFT 6 + +/* Sample rate generator register */ +#define SCKDIV_SHIFT 0 +#define FRWID_SHIFT 10 +#define FRPER_SHIFT 16 + +#define SCK_DIV_MASK 0x0000003FF +#define FRAME_WIDTH_BITS(n) (((n) << FRWID_SHIFT) & 0x0000FC00) +#define FRAME_PERIOD_BITS(n) (((n) << FRPER_SHIFT) & 0x1FFF0000) + +/* DMA controller register */ +#define RX_DMA_ENABLE BIT(0) +#define TX_DMA_ENABLE BIT(1) + +#define RDMAE_SHIFT 0 +#define TDMAE_SHIFT 1 + +/* Interrupt Register */ +#define RX_SERVICE_INT BIT(0) +#define RX_OVERRUN_ERROR_INT BIT(1) +#define RX_FSYNC_ERR_INT BIT(2) +#define RX_FSYNC_INT BIT(3) +#define TX_SERVICE_INT BIT(4) +#define TX_UNDERRUN_ERR_INT BIT(5) +#define TX_FSYNC_ERR_INT BIT(6) +#define TX_FSYNC_INT BIT(7) +#define ALL_INT 0x000000ff + +/* MSP test control register */ +#define MSP_ITCR_ITEN BIT(0) +#define MSP_ITCR_TESTFIFO BIT(1) + +#define RMCEN_BIT 0 +#define RMCSF_BIT 1 +#define RCMPM_BIT 3 +#define TMCEN_BIT 5 +#define TNCSF_BIT 6 + +/* Single or dual phase mode */ +enum msp_phase_mode { + MSP_SINGLE_PHASE, + MSP_DUAL_PHASE +}; + +/* Frame length */ +enum msp_frame_length { + MSP_FRAME_LEN_1 = 0, + MSP_FRAME_LEN_2 = 1, + MSP_FRAME_LEN_4 = 3, + MSP_FRAME_LEN_8 = 7, + MSP_FRAME_LEN_12 = 11, + MSP_FRAME_LEN_16 = 15, + MSP_FRAME_LEN_20 = 19, + MSP_FRAME_LEN_32 = 31, + MSP_FRAME_LEN_48 = 47, + MSP_FRAME_LEN_64 = 63 +}; + +/* Element length */ +enum msp_elem_length { + MSP_ELEM_LEN_8 = 0, + MSP_ELEM_LEN_10 = 1, + MSP_ELEM_LEN_12 = 2, + MSP_ELEM_LEN_14 = 3, + MSP_ELEM_LEN_16 = 4, + MSP_ELEM_LEN_20 = 5, + MSP_ELEM_LEN_24 = 6, + MSP_ELEM_LEN_32 = 7 +}; + +enum msp_data_xfer_width { + MSP_DATA_TRANSFER_WIDTH_BYTE, + MSP_DATA_TRANSFER_WIDTH_HALFWORD, + MSP_DATA_TRANSFER_WIDTH_WORD +}; + +enum msp_frame_sync { + MSP_FSYNC_UNIGNORE = 0, + MSP_FSYNC_IGNORE = 1, +}; + +enum msp_phase2_start_mode { + MSP_PHASE2_START_MODE_IMEDIATE, + MSP_PHASE2_START_MODE_FSYNC +}; + +enum msp_btf { + MSP_BTF_MS_BIT_FIRST = 0, + MSP_BTF_LS_BIT_FIRST = 1 +}; + +enum msp_fsync_pol { + MSP_FSYNC_POL_ACT_HI = 0, + MSP_FSYNC_POL_ACT_LO = 1 +}; + +/* Data delay (in bit clock cycles) */ +enum msp_delay { + MSP_DELAY_0 = 0, + MSP_DELAY_1 = 1, + MSP_DELAY_2 = 2, + MSP_DELAY_3 = 3 +}; + +/* Configurations of clocks (transmit, receive or sample rate generator) */ +enum msp_edge { + MSP_FALLING_EDGE = 0, + MSP_RISING_EDGE = 1, +}; + +enum msp_hws { + MSP_SWAP_NONE = 0, + MSP_SWAP_BYTE_PER_WORD = 1, + MSP_SWAP_BYTE_PER_HALF_WORD = 2, + MSP_SWAP_HALF_WORD_PER_WORD = 3 +}; + +enum msp_compress_mode { + MSP_COMPRESS_MODE_LINEAR = 0, + MSP_COMPRESS_MODE_MU_LAW = 2, + MSP_COMPRESS_MODE_A_LAW = 3 +}; + +enum msp_spi_burst_mode { + MSP_SPI_BURST_MODE_DISABLE = 0, + MSP_SPI_BURST_MODE_ENABLE = 1 +}; + +enum msp_expand_mode { + MSP_EXPAND_MODE_LINEAR = 0, + MSP_EXPAND_MODE_LINEAR_SIGNED = 1, + MSP_EXPAND_MODE_MU_LAW = 2, + MSP_EXPAND_MODE_A_LAW = 3 +}; + +#define MSP_FRAME_PERIOD_IN_MONO_MODE 256 +#define MSP_FRAME_PERIOD_IN_STEREO_MODE 32 +#define MSP_FRAME_WIDTH_IN_STEREO_MODE 16 + +enum msp_protocol { + MSP_I2S_PROTOCOL, + MSP_PCM_PROTOCOL, + MSP_PCM_COMPAND_PROTOCOL, + MSP_INVALID_PROTOCOL +}; + +/* + * No of registers to backup during + * suspend resume + */ +#define MAX_MSP_BACKUP_REGS 36 + +enum enum_i2s_controller { + MSP_0_I2S_CONTROLLER = 0, + MSP_1_I2S_CONTROLLER, + MSP_2_I2S_CONTROLLER, + MSP_3_I2S_CONTROLLER, +}; + +enum i2s_direction_t { + MSP_DIR_TX = 0x01, + MSP_DIR_RX = 0x02, +}; + +enum msp_data_size { + MSP_DATA_BITS_DEFAULT = -1, + MSP_DATA_BITS_8 = 0x00, + MSP_DATA_BITS_10, + MSP_DATA_BITS_12, + MSP_DATA_BITS_14, + MSP_DATA_BITS_16, + MSP_DATA_BITS_20, + MSP_DATA_BITS_24, + MSP_DATA_BITS_32, +}; + +enum msp_state { + MSP_STATE_IDLE = 0, + MSP_STATE_CONFIGURED = 1, + MSP_STATE_RUNNING = 2, +}; + +enum msp_rx_comparison_enable_mode { + MSP_COMPARISON_DISABLED = 0, + MSP_COMPARISON_NONEQUAL_ENABLED = 2, + MSP_COMPARISON_EQUAL_ENABLED = 3 +}; + +struct msp_multichannel_config { + bool rx_multichannel_enable; + bool tx_multichannel_enable; + enum msp_rx_comparison_enable_mode rx_comparison_enable_mode; + u8 padding; + u32 comparison_value; + u32 comparison_mask; + u32 rx_channel_0_enable; + u32 rx_channel_1_enable; + u32 rx_channel_2_enable; + u32 rx_channel_3_enable; + u32 tx_channel_0_enable; + u32 tx_channel_1_enable; + u32 tx_channel_2_enable; + u32 tx_channel_3_enable; +}; + +struct msp_protdesc { + u32 rx_phase_mode; + u32 tx_phase_mode; + u32 rx_phase2_start_mode; + u32 tx_phase2_start_mode; + u32 rx_byte_order; + u32 tx_byte_order; + u32 rx_frame_len_1; + u32 rx_frame_len_2; + u32 tx_frame_len_1; + u32 tx_frame_len_2; + u32 rx_elem_len_1; + u32 rx_elem_len_2; + u32 tx_elem_len_1; + u32 tx_elem_len_2; + u32 rx_data_delay; + u32 tx_data_delay; + u32 rx_clk_pol; + u32 tx_clk_pol; + u32 rx_fsync_pol; + u32 tx_fsync_pol; + u32 rx_half_word_swap; + u32 tx_half_word_swap; + u32 compression_mode; + u32 expansion_mode; + u32 frame_sync_ignore; + u32 frame_period; + u32 frame_width; + u32 clocks_per_frame; +}; + +struct i2s_message { + enum i2s_direction_t i2s_direction; + void *txdata; + void *rxdata; + size_t txbytes; + size_t rxbytes; + int dma_flag; + int tx_offset; + int rx_offset; + bool cyclic_dma; + dma_addr_t buf_addr; + size_t buf_len; + size_t period_len; +}; + +struct i2s_controller { + struct module *owner; + unsigned int id; + unsigned int class; + const struct i2s_algorithm *algo; /* the algorithm to access the bus */ + void *data; + struct mutex bus_lock; + struct device dev; /* the controller device */ + char name[48]; +}; + +struct ux500_msp_config { + unsigned int f_inputclk; + unsigned int rx_clk_sel; + unsigned int tx_clk_sel; + unsigned int srg_clk_sel; + unsigned int rx_fsync_pol; + unsigned int tx_fsync_pol; + unsigned int rx_fsync_sel; + unsigned int tx_fsync_sel; + unsigned int rx_fifo_config; + unsigned int tx_fifo_config; + unsigned int spi_clk_mode; + unsigned int spi_burst_mode; + unsigned int loopback_enable; + unsigned int tx_data_enable; + unsigned int default_protdesc; + struct msp_protdesc protdesc; + int multichannel_configured; + struct msp_multichannel_config multichannel_config; + unsigned int direction; + unsigned int protocol; + unsigned int frame_freq; + unsigned int frame_size; + enum msp_data_size data_size; + unsigned int def_elem_len; + unsigned int iodelay; + void (*handler) (void *data); + void *tx_callback_data; + void *rx_callback_data; +}; + +struct ux500_msp { + enum enum_i2s_controller id; + void __iomem *registers; + struct device *dev; + struct i2s_controller *i2s_cont; + struct stedma40_chan_cfg *dma_cfg_rx; + struct stedma40_chan_cfg *dma_cfg_tx; + struct dma_chan *tx_pipeid; + struct dma_chan *rx_pipeid; + enum msp_state msp_state; + int (*transfer) (struct ux500_msp *msp, struct i2s_message *message); + int (*plat_init) (void); + int (*plat_exit) (void); + struct timer_list notify_timer; + int def_elem_len; + unsigned int dir_busy; + int loopback_enable; + u32 backup_regs[MAX_MSP_BACKUP_REGS]; + unsigned int f_bitclk; +}; + +struct ux500_msp_dma_params { + unsigned int data_size; + struct stedma40_chan_cfg *dma_cfg; +}; + +int ux500_msp_i2s_init_msp(struct platform_device *pdev, + struct ux500_msp **msp_p, + struct msp_i2s_platform_data *platform_data); +void ux500_msp_i2s_cleanup_msp(struct platform_device *pdev, + struct ux500_msp *msp); +int ux500_msp_i2s_open(struct ux500_msp *msp, struct ux500_msp_config *config); +int ux500_msp_i2s_close(struct ux500_msp *msp, + unsigned int dir); +int ux500_msp_i2s_trigger(struct ux500_msp *msp, int cmd, + int direction); + +#endif |