diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 10:05:51 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 10:05:51 +0000 |
commit | 5d1646d90e1f2cceb9f0828f4b28318cd0ec7744 (patch) | |
tree | a94efe259b9009378be6d90eb30d2b019d95c194 /sound/soc/tegra | |
parent | Initial commit. (diff) | |
download | linux-5d1646d90e1f2cceb9f0828f4b28318cd0ec7744.tar.xz linux-5d1646d90e1f2cceb9f0828f4b28318cd0ec7744.zip |
Adding upstream version 5.10.209.upstream/5.10.209
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'sound/soc/tegra')
38 files changed, 11740 insertions, 0 deletions
diff --git a/sound/soc/tegra/Kconfig b/sound/soc/tegra/Kconfig new file mode 100644 index 000000000..3d91bd3e5 --- /dev/null +++ b/sound/soc/tegra/Kconfig @@ -0,0 +1,194 @@ +# SPDX-License-Identifier: GPL-2.0-only +config SND_SOC_TEGRA + tristate "SoC Audio for the Tegra System-on-Chip" + depends on (ARCH_TEGRA && TEGRA20_APB_DMA) || COMPILE_TEST + depends on COMMON_CLK + depends on RESET_CONTROLLER + select REGMAP_MMIO + select SND_SOC_GENERIC_DMAENGINE_PCM + help + Say Y or M here if you want support for SoC audio on Tegra. + +config SND_SOC_TEGRA20_AC97 + tristate "Tegra20 AC97 interface" + depends on SND_SOC_TEGRA + select SND_SOC_AC97_BUS + select SND_SOC_TEGRA20_DAS + help + Say Y or M if you want to add support for codecs attached to the + Tegra20 AC97 interface. You will also need to select the individual + machine drivers to support below. + +config SND_SOC_TEGRA20_DAS + tristate "Tegra20 DAS module" + depends on SND_SOC_TEGRA + 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 "Tegra20 I2S interface" + depends on SND_SOC_TEGRA + select SND_SOC_TEGRA20_DAS + help + Say Y or M if you want to add support for codecs attached to the + Tegra20 I2S interface. You will also need to select the individual + machine drivers to support below. + +config SND_SOC_TEGRA20_SPDIF + tristate "Tegra20 SPDIF interface" + depends on SND_SOC_TEGRA + default m + help + 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 "Tegra30 AHUB module" + depends on SND_SOC_TEGRA + help + Say Y or M if you want to add support for the Tegra30 AHUB module. + You will also need to select the individual machine drivers to + support below. + +config SND_SOC_TEGRA30_I2S + tristate "Tegra30 I2S interface" + depends on SND_SOC_TEGRA + 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_TEGRA210_AHUB + tristate "Tegra210 AHUB module" + depends on SND_SOC_TEGRA + help + Config to enable Audio Hub (AHUB) module, which comprises of a + switch called Audio Crossbar (AXBAR) used to configure or modify + the audio routing path between various HW accelerators present in + AHUB. + Say Y or M if you want to add support for Tegra210 AHUB module. + +config SND_SOC_TEGRA210_DMIC + tristate "Tegra210 DMIC module" + depends on SND_SOC_TEGRA + help + Config to enable the Digital MIC (DMIC) controller which is used + to interface with Pulse Density Modulation (PDM) input devices. + The DMIC controller implements a converter to convert PDM signals + to Pulse Code Modulation (PCM) signals. This can be viewed as a + PDM receiver. + Say Y or M if you want to add support for Tegra210 DMIC module. + +config SND_SOC_TEGRA210_I2S + tristate "Tegra210 I2S module" + depends on SND_SOC_TEGRA + help + Config to enable the Inter-IC Sound (I2S) Controller which + implements full-duplex and bidirectional and single direction + point-to-point serial interfaces. It can interface with I2S + compatible devices. + Say Y or M if you want to add support for Tegra210 I2S module. + +config SND_SOC_TEGRA186_DSPK + tristate "Tegra186 DSPK module" + depends on SND_SOC_TEGRA + help + Config to enable the Digital Speaker Controller (DSPK) which + converts the multi-bit Pulse Code Modulation (PCM) audio input to + oversampled 1-bit Pulse Density Modulation (PDM) output. From the + signal flow perspective DSPK can be viewed as a PDM transmitter + that up-samples the input to the desired sampling rate by + interpolation and then converts the oversampled PCM input to + the desired 1-bit output via Delta Sigma Modulation (DSM). + Say Y or M if you want to add support for Tegra186 DSPK module. + +config SND_SOC_TEGRA210_ADMAIF + tristate "Tegra210 ADMAIF module" + depends on SND_SOC_TEGRA + help + Config to enable ADMAIF which is the interface between ADMA and + Audio Hub (AHUB). Each ADMA channel that sends/receives data to/ + from AHUB must interface through an ADMAIF channel. ADMA channel + sending data to AHUB pairs with an ADMAIF Tx channel, where as + ADMA channel receiving data from AHUB pairs with an ADMAIF Rx + channel. Buffer size is configurable for each ADMAIIF channel. + Say Y or M if you want to add support for Tegra210 ADMAIF module. + +config SND_SOC_TEGRA_RT5640 + tristate "SoC Audio support for Tegra boards using an RT5640 codec" + depends on SND_SOC_TEGRA && I2C && GPIOLIB + select SND_SOC_RT5640 + help + Say Y or M here if you want to add support for SoC audio on Tegra + boards using the RT5640 codec, such as Dalmore. + +config SND_SOC_TEGRA_WM8753 + tristate "SoC Audio support for Tegra boards using a WM8753 codec" + depends on SND_SOC_TEGRA && I2C && GPIOLIB + 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 SND_SOC_TEGRA_WM8903 + tristate "SoC Audio support for Tegra boards using a WM8903 codec" + depends on SND_SOC_TEGRA && I2C && GPIOLIB + select SND_SOC_WM8903 + help + Say Y or M here if you want to add support for SoC audio on Tegra + boards using the WM8093 codec. Currently, the supported boards are + Harmony, Ventana, Seaboard, Kaen, and Aebl. + +config SND_SOC_TEGRA_WM9712 + tristate "SoC Audio support for Tegra boards using a WM9712 codec" + depends on SND_SOC_TEGRA && GPIOLIB + select SND_SOC_TEGRA20_AC97 + select SND_SOC_WM9712 + help + Say Y or M here if you want to add support for SoC audio on Tegra + boards using the WM9712 (or compatible) codec. + +config SND_SOC_TEGRA_TRIMSLICE + tristate "SoC Audio support for TrimSlice board" + depends on SND_SOC_TEGRA && I2C + select SND_SOC_TLV320AIC23_I2C + 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 && GPIOLIB + select SND_SOC_ALC5632 + help + Say Y or M here if you want to add support for SoC audio on the + Toshiba AC100 netbook. + +config SND_SOC_TEGRA_MAX98090 + tristate "SoC Audio support for Tegra boards using a MAX98090 codec" + depends on SND_SOC_TEGRA && I2C && GPIOLIB + select SND_SOC_MAX98090 + help + Say Y or M here if you want to add support for SoC audio on Tegra + boards using the MAX98090 codec, such as Venice2. + +config SND_SOC_TEGRA_RT5677 + tristate "SoC Audio support for Tegra boards using a RT5677 codec" + depends on SND_SOC_TEGRA && I2C && GPIOLIB + select SND_SOC_RT5677 + help + Say Y or M here if you want to add support for SoC audio on Tegra + boards using the RT5677 codec, such as Ryu. + +config SND_SOC_TEGRA_SGTL5000 + tristate "SoC Audio support for Tegra boards using a SGTL5000 codec" + depends on SND_SOC_TEGRA && I2C && GPIOLIB + select SND_SOC_SGTL5000 + help + Say Y or M here if you want to add support for SoC audio on Tegra + boards using the SGTL5000 codec, such as Apalis T30, Apalis TK1 or + Colibri T30. diff --git a/sound/soc/tegra/Makefile b/sound/soc/tegra/Makefile new file mode 100644 index 000000000..60040a06b --- /dev/null +++ b/sound/soc/tegra/Makefile @@ -0,0 +1,50 @@ +# SPDX-License-Identifier: GPL-2.0 +# Tegra platform Support +snd-soc-tegra-pcm-objs := tegra_pcm.o +snd-soc-tegra-utils-objs += tegra_asoc_utils.o +snd-soc-tegra20-ac97-objs := tegra20_ac97.o +snd-soc-tegra20-das-objs := tegra20_das.o +snd-soc-tegra20-i2s-objs := tegra20_i2s.o +snd-soc-tegra20-spdif-objs := tegra20_spdif.o +snd-soc-tegra30-ahub-objs := tegra30_ahub.o +snd-soc-tegra30-i2s-objs := tegra30_i2s.o +snd-soc-tegra210-ahub-objs := tegra210_ahub.o +snd-soc-tegra210-dmic-objs := tegra210_dmic.o +snd-soc-tegra210-i2s-objs := tegra210_i2s.o +snd-soc-tegra186-dspk-objs := tegra186_dspk.o +snd-soc-tegra210-admaif-objs := tegra210_admaif.o + +obj-$(CONFIG_SND_SOC_TEGRA) += snd-soc-tegra-pcm.o +obj-$(CONFIG_SND_SOC_TEGRA) += snd-soc-tegra-utils.o +obj-$(CONFIG_SND_SOC_TEGRA20_AC97) += snd-soc-tegra20-ac97.o +obj-$(CONFIG_SND_SOC_TEGRA20_DAS) += snd-soc-tegra20-das.o +obj-$(CONFIG_SND_SOC_TEGRA20_I2S) += snd-soc-tegra20-i2s.o +obj-$(CONFIG_SND_SOC_TEGRA20_SPDIF) += snd-soc-tegra20-spdif.o +obj-$(CONFIG_SND_SOC_TEGRA30_AHUB) += snd-soc-tegra30-ahub.o +obj-$(CONFIG_SND_SOC_TEGRA30_I2S) += snd-soc-tegra30-i2s.o +obj-$(CONFIG_SND_SOC_TEGRA210_DMIC) += snd-soc-tegra210-dmic.o +obj-$(CONFIG_SND_SOC_TEGRA210_AHUB) += snd-soc-tegra210-ahub.o +obj-$(CONFIG_SND_SOC_TEGRA210_I2S) += snd-soc-tegra210-i2s.o +obj-$(CONFIG_SND_SOC_TEGRA186_DSPK) += snd-soc-tegra186-dspk.o +obj-$(CONFIG_SND_SOC_TEGRA210_ADMAIF) += snd-soc-tegra210-admaif.o + +# Tegra machine Support +snd-soc-tegra-rt5640-objs := tegra_rt5640.o +snd-soc-tegra-rt5677-objs := tegra_rt5677.o +snd-soc-tegra-wm8753-objs := tegra_wm8753.o +snd-soc-tegra-wm8903-objs := tegra_wm8903.o +snd-soc-tegra-wm9712-objs := tegra_wm9712.o +snd-soc-tegra-trimslice-objs := trimslice.o +snd-soc-tegra-alc5632-objs := tegra_alc5632.o +snd-soc-tegra-max98090-objs := tegra_max98090.o +snd-soc-tegra-sgtl5000-objs := tegra_sgtl5000.o + +obj-$(CONFIG_SND_SOC_TEGRA_RT5640) += snd-soc-tegra-rt5640.o +obj-$(CONFIG_SND_SOC_TEGRA_RT5677) += snd-soc-tegra-rt5677.o +obj-$(CONFIG_SND_SOC_TEGRA_WM8753) += snd-soc-tegra-wm8753.o +obj-$(CONFIG_SND_SOC_TEGRA_WM8903) += snd-soc-tegra-wm8903.o +obj-$(CONFIG_SND_SOC_TEGRA_WM9712) += snd-soc-tegra-wm9712.o +obj-$(CONFIG_SND_SOC_TEGRA_TRIMSLICE) += snd-soc-tegra-trimslice.o +obj-$(CONFIG_SND_SOC_TEGRA_ALC5632) += snd-soc-tegra-alc5632.o +obj-$(CONFIG_SND_SOC_TEGRA_MAX98090) += snd-soc-tegra-max98090.o +obj-$(CONFIG_SND_SOC_TEGRA_SGTL5000) += snd-soc-tegra-sgtl5000.o diff --git a/sound/soc/tegra/tegra186_dspk.c b/sound/soc/tegra/tegra186_dspk.c new file mode 100644 index 000000000..373189e59 --- /dev/null +++ b/sound/soc/tegra/tegra186_dspk.c @@ -0,0 +1,555 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// tegra186_dspk.c - Tegra186 DSPK driver +// +// Copyright (c) 2020 NVIDIA CORPORATION. All rights reserved. + +#include <linux/clk.h> +#include <linux/device.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <sound/core.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include "tegra186_dspk.h" +#include "tegra_cif.h" + +static const struct reg_default tegra186_dspk_reg_defaults[] = { + { TEGRA186_DSPK_RX_INT_MASK, 0x00000007 }, + { TEGRA186_DSPK_RX_CIF_CTRL, 0x00007700 }, + { TEGRA186_DSPK_CG, 0x00000001 }, + { TEGRA186_DSPK_CORE_CTRL, 0x00000310 }, + { TEGRA186_DSPK_CODEC_CTRL, 0x03000000 }, +}; + +static int tegra186_dspk_get_fifo_th(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *codec = snd_soc_kcontrol_component(kcontrol); + struct tegra186_dspk *dspk = snd_soc_component_get_drvdata(codec); + + ucontrol->value.integer.value[0] = dspk->rx_fifo_th; + + return 0; +} + +static int tegra186_dspk_put_fifo_th(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *codec = snd_soc_kcontrol_component(kcontrol); + struct tegra186_dspk *dspk = snd_soc_component_get_drvdata(codec); + int value = ucontrol->value.integer.value[0]; + + if (value == dspk->rx_fifo_th) + return 0; + + dspk->rx_fifo_th = value; + + return 1; +} + +static int tegra186_dspk_get_osr_val(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *codec = snd_soc_kcontrol_component(kcontrol); + struct tegra186_dspk *dspk = snd_soc_component_get_drvdata(codec); + + ucontrol->value.enumerated.item[0] = dspk->osr_val; + + return 0; +} + +static int tegra186_dspk_put_osr_val(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *codec = snd_soc_kcontrol_component(kcontrol); + struct tegra186_dspk *dspk = snd_soc_component_get_drvdata(codec); + unsigned int value = ucontrol->value.enumerated.item[0]; + + if (value == dspk->osr_val) + return 0; + + dspk->osr_val = value; + + return 1; +} + +static int tegra186_dspk_get_pol_sel(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *codec = snd_soc_kcontrol_component(kcontrol); + struct tegra186_dspk *dspk = snd_soc_component_get_drvdata(codec); + + ucontrol->value.enumerated.item[0] = dspk->lrsel; + + return 0; +} + +static int tegra186_dspk_put_pol_sel(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *codec = snd_soc_kcontrol_component(kcontrol); + struct tegra186_dspk *dspk = snd_soc_component_get_drvdata(codec); + unsigned int value = ucontrol->value.enumerated.item[0]; + + if (value == dspk->lrsel) + return 0; + + dspk->lrsel = value; + + return 1; +} + +static int tegra186_dspk_get_ch_sel(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *codec = snd_soc_kcontrol_component(kcontrol); + struct tegra186_dspk *dspk = snd_soc_component_get_drvdata(codec); + + ucontrol->value.enumerated.item[0] = dspk->ch_sel; + + return 0; +} + +static int tegra186_dspk_put_ch_sel(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *codec = snd_soc_kcontrol_component(kcontrol); + struct tegra186_dspk *dspk = snd_soc_component_get_drvdata(codec); + unsigned int value = ucontrol->value.enumerated.item[0]; + + if (value == dspk->ch_sel) + return 0; + + dspk->ch_sel = value; + + return 1; +} + +static int tegra186_dspk_get_mono_to_stereo(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *codec = snd_soc_kcontrol_component(kcontrol); + struct tegra186_dspk *dspk = snd_soc_component_get_drvdata(codec); + + ucontrol->value.enumerated.item[0] = dspk->mono_to_stereo; + + return 0; +} + +static int tegra186_dspk_put_mono_to_stereo(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *codec = snd_soc_kcontrol_component(kcontrol); + struct tegra186_dspk *dspk = snd_soc_component_get_drvdata(codec); + unsigned int value = ucontrol->value.enumerated.item[0]; + + if (value == dspk->mono_to_stereo) + return 0; + + dspk->mono_to_stereo = value; + + return 1; +} + +static int tegra186_dspk_get_stereo_to_mono(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *codec = snd_soc_kcontrol_component(kcontrol); + struct tegra186_dspk *dspk = snd_soc_component_get_drvdata(codec); + + ucontrol->value.enumerated.item[0] = dspk->stereo_to_mono; + + return 0; +} + +static int tegra186_dspk_put_stereo_to_mono(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *codec = snd_soc_kcontrol_component(kcontrol); + struct tegra186_dspk *dspk = snd_soc_component_get_drvdata(codec); + unsigned int value = ucontrol->value.enumerated.item[0]; + + if (value == dspk->stereo_to_mono) + return 0; + + dspk->stereo_to_mono = value; + + return 1; +} + +static int __maybe_unused tegra186_dspk_runtime_suspend(struct device *dev) +{ + struct tegra186_dspk *dspk = dev_get_drvdata(dev); + + regcache_cache_only(dspk->regmap, true); + regcache_mark_dirty(dspk->regmap); + + clk_disable_unprepare(dspk->clk_dspk); + + return 0; +} + +static int __maybe_unused tegra186_dspk_runtime_resume(struct device *dev) +{ + struct tegra186_dspk *dspk = dev_get_drvdata(dev); + int err; + + err = clk_prepare_enable(dspk->clk_dspk); + if (err) { + dev_err(dev, "failed to enable DSPK clock, err: %d\n", err); + return err; + } + + regcache_cache_only(dspk->regmap, false); + regcache_sync(dspk->regmap); + + return 0; +} + +static int tegra186_dspk_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct tegra186_dspk *dspk = snd_soc_dai_get_drvdata(dai); + unsigned int channels, srate, dspk_clk; + struct device *dev = dai->dev; + struct tegra_cif_conf cif_conf; + unsigned int max_th; + int err; + + memset(&cif_conf, 0, sizeof(struct tegra_cif_conf)); + + channels = params_channels(params); + cif_conf.audio_ch = channels; + + /* Client channel */ + switch (dspk->ch_sel) { + case DSPK_CH_SELECT_LEFT: + case DSPK_CH_SELECT_RIGHT: + cif_conf.client_ch = 1; + break; + case DSPK_CH_SELECT_STEREO: + cif_conf.client_ch = 2; + break; + default: + dev_err(dev, "Invalid DSPK client channels\n"); + return -EINVAL; + } + + cif_conf.client_bits = TEGRA_ACIF_BITS_24; + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + cif_conf.audio_bits = TEGRA_ACIF_BITS_16; + break; + case SNDRV_PCM_FORMAT_S32_LE: + cif_conf.audio_bits = TEGRA_ACIF_BITS_32; + break; + default: + dev_err(dev, "unsupported format!\n"); + return -EOPNOTSUPP; + } + + srate = params_rate(params); + + /* RX FIFO threshold in terms of frames */ + max_th = (TEGRA186_DSPK_RX_FIFO_DEPTH / cif_conf.audio_ch) - 1; + + if (dspk->rx_fifo_th > max_th) + dspk->rx_fifo_th = max_th; + + cif_conf.threshold = dspk->rx_fifo_th; + cif_conf.mono_conv = dspk->mono_to_stereo; + cif_conf.stereo_conv = dspk->stereo_to_mono; + + tegra_set_cif(dspk->regmap, TEGRA186_DSPK_RX_CIF_CTRL, + &cif_conf); + + /* + * DSPK clock and PDM codec clock should be synchronous with 4:1 ratio, + * this is because it takes 4 clock cycles to send out one sample to + * codec by sigma delta modulator. Finally the clock rate is a multiple + * of 'Over Sampling Ratio', 'Sample Rate' and 'Interface Clock Ratio'. + */ + dspk_clk = (DSPK_OSR_FACTOR << dspk->osr_val) * srate * DSPK_CLK_RATIO; + + err = clk_set_rate(dspk->clk_dspk, dspk_clk); + if (err) { + dev_err(dev, "can't set DSPK clock rate %u, err: %d\n", + dspk_clk, err); + + return err; + } + + regmap_update_bits(dspk->regmap, + /* Reg */ + TEGRA186_DSPK_CORE_CTRL, + /* Mask */ + TEGRA186_DSPK_OSR_MASK | + TEGRA186_DSPK_CHANNEL_SELECT_MASK | + TEGRA186_DSPK_CTRL_LRSEL_POLARITY_MASK, + /* Value */ + (dspk->osr_val << DSPK_OSR_SHIFT) | + ((dspk->ch_sel + 1) << CH_SEL_SHIFT) | + (dspk->lrsel << LRSEL_POL_SHIFT)); + + return 0; +} + +static const struct snd_soc_dai_ops tegra186_dspk_dai_ops = { + .hw_params = tegra186_dspk_hw_params, +}; + +static struct snd_soc_dai_driver tegra186_dspk_dais[] = { + { + .name = "DSPK-CIF", + .playback = { + .stream_name = "CIF-Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S32_LE, + }, + }, + { + .name = "DSPK-DAP", + .playback = { + .stream_name = "DAP-Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S32_LE, + }, + .ops = &tegra186_dspk_dai_ops, + .symmetric_rates = 1, + }, +}; + +static const struct snd_soc_dapm_widget tegra186_dspk_widgets[] = { + SND_SOC_DAPM_AIF_IN("RX", NULL, 0, TEGRA186_DSPK_ENABLE, 0, 0), + SND_SOC_DAPM_SPK("SPK", NULL), +}; + +static const struct snd_soc_dapm_route tegra186_dspk_routes[] = { + { "XBAR-Playback", NULL, "XBAR-TX" }, + { "CIF-Playback", NULL, "XBAR-Playback" }, + { "RX", NULL, "CIF-Playback" }, + { "DAP-Playback", NULL, "RX" }, + { "SPK", NULL, "DAP-Playback" }, +}; + +static const char * const tegra186_dspk_ch_sel_text[] = { + "Left", "Right", "Stereo", +}; + +static const struct soc_enum tegra186_dspk_ch_sel_enum = + SOC_ENUM_SINGLE(SND_SOC_NOPM, 0, ARRAY_SIZE(tegra186_dspk_ch_sel_text), + tegra186_dspk_ch_sel_text); + +static const char * const tegra186_dspk_osr_text[] = { + "OSR_32", "OSR_64", "OSR_128", "OSR_256", +}; + +static const struct soc_enum tegra186_dspk_osr_enum = + SOC_ENUM_SINGLE(SND_SOC_NOPM, 0, ARRAY_SIZE(tegra186_dspk_osr_text), + tegra186_dspk_osr_text); + +static const char * const tegra186_dspk_lrsel_text[] = { + "Left", "Right", +}; + +static const char * const tegra186_dspk_mono_conv_text[] = { + "Zero", "Copy", +}; + +static const struct soc_enum tegra186_dspk_mono_conv_enum = + SOC_ENUM_SINGLE(SND_SOC_NOPM, 0, + ARRAY_SIZE(tegra186_dspk_mono_conv_text), + tegra186_dspk_mono_conv_text); + +static const char * const tegra186_dspk_stereo_conv_text[] = { + "CH0", "CH1", "AVG", +}; + +static const struct soc_enum tegra186_dspk_stereo_conv_enum = + SOC_ENUM_SINGLE(SND_SOC_NOPM, 0, + ARRAY_SIZE(tegra186_dspk_stereo_conv_text), + tegra186_dspk_stereo_conv_text); + +static const struct soc_enum tegra186_dspk_lrsel_enum = + SOC_ENUM_SINGLE(SND_SOC_NOPM, 0, ARRAY_SIZE(tegra186_dspk_lrsel_text), + tegra186_dspk_lrsel_text); + +static const struct snd_kcontrol_new tegrat186_dspk_controls[] = { + SOC_SINGLE_EXT("FIFO Threshold", SND_SOC_NOPM, 0, + TEGRA186_DSPK_RX_FIFO_DEPTH - 1, 0, + tegra186_dspk_get_fifo_th, tegra186_dspk_put_fifo_th), + SOC_ENUM_EXT("OSR Value", tegra186_dspk_osr_enum, + tegra186_dspk_get_osr_val, tegra186_dspk_put_osr_val), + SOC_ENUM_EXT("LR Polarity Select", tegra186_dspk_lrsel_enum, + tegra186_dspk_get_pol_sel, tegra186_dspk_put_pol_sel), + SOC_ENUM_EXT("Channel Select", tegra186_dspk_ch_sel_enum, + tegra186_dspk_get_ch_sel, tegra186_dspk_put_ch_sel), + SOC_ENUM_EXT("Mono To Stereo", tegra186_dspk_mono_conv_enum, + tegra186_dspk_get_mono_to_stereo, + tegra186_dspk_put_mono_to_stereo), + SOC_ENUM_EXT("Stereo To Mono", tegra186_dspk_stereo_conv_enum, + tegra186_dspk_get_stereo_to_mono, + tegra186_dspk_put_stereo_to_mono), +}; + +static const struct snd_soc_component_driver tegra186_dspk_cmpnt = { + .dapm_widgets = tegra186_dspk_widgets, + .num_dapm_widgets = ARRAY_SIZE(tegra186_dspk_widgets), + .dapm_routes = tegra186_dspk_routes, + .num_dapm_routes = ARRAY_SIZE(tegra186_dspk_routes), + .controls = tegrat186_dspk_controls, + .num_controls = ARRAY_SIZE(tegrat186_dspk_controls), +}; + +static bool tegra186_dspk_wr_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TEGRA186_DSPK_RX_INT_MASK ... TEGRA186_DSPK_RX_CIF_CTRL: + case TEGRA186_DSPK_ENABLE ... TEGRA186_DSPK_CG: + case TEGRA186_DSPK_CORE_CTRL ... TEGRA186_DSPK_CODEC_CTRL: + return true; + default: + return false; + }; +} + +static bool tegra186_dspk_rd_reg(struct device *dev, unsigned int reg) +{ + if (tegra186_dspk_wr_reg(dev, reg)) + return true; + + switch (reg) { + case TEGRA186_DSPK_RX_STATUS: + case TEGRA186_DSPK_RX_INT_STATUS: + case TEGRA186_DSPK_STATUS: + case TEGRA186_DSPK_INT_STATUS: + return true; + default: + return false; + }; +} + +static bool tegra186_dspk_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TEGRA186_DSPK_RX_STATUS: + case TEGRA186_DSPK_RX_INT_STATUS: + case TEGRA186_DSPK_STATUS: + case TEGRA186_DSPK_INT_STATUS: + return true; + default: + return false; + }; +} + +static const struct regmap_config tegra186_dspk_regmap = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = TEGRA186_DSPK_CODEC_CTRL, + .writeable_reg = tegra186_dspk_wr_reg, + .readable_reg = tegra186_dspk_rd_reg, + .volatile_reg = tegra186_dspk_volatile_reg, + .reg_defaults = tegra186_dspk_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(tegra186_dspk_reg_defaults), + .cache_type = REGCACHE_FLAT, +}; + +static const struct of_device_id tegra186_dspk_of_match[] = { + { .compatible = "nvidia,tegra186-dspk" }, + {}, +}; +MODULE_DEVICE_TABLE(of, tegra186_dspk_of_match); + +static int tegra186_dspk_platform_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct tegra186_dspk *dspk; + void __iomem *regs; + int err; + + dspk = devm_kzalloc(dev, sizeof(*dspk), GFP_KERNEL); + if (!dspk) + return -ENOMEM; + + dspk->osr_val = DSPK_OSR_64; + dspk->lrsel = DSPK_LRSEL_LEFT; + dspk->ch_sel = DSPK_CH_SELECT_STEREO; + dspk->mono_to_stereo = 0; /* "Zero" */ + + dev_set_drvdata(dev, dspk); + + dspk->clk_dspk = devm_clk_get(dev, "dspk"); + if (IS_ERR(dspk->clk_dspk)) { + dev_err(dev, "can't retrieve DSPK clock\n"); + return PTR_ERR(dspk->clk_dspk); + } + + regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + dspk->regmap = devm_regmap_init_mmio(dev, regs, &tegra186_dspk_regmap); + if (IS_ERR(dspk->regmap)) { + dev_err(dev, "regmap init failed\n"); + return PTR_ERR(dspk->regmap); + } + + regcache_cache_only(dspk->regmap, true); + + err = devm_snd_soc_register_component(dev, &tegra186_dspk_cmpnt, + tegra186_dspk_dais, + ARRAY_SIZE(tegra186_dspk_dais)); + if (err) { + dev_err(dev, "can't register DSPK component, err: %d\n", + err); + return err; + } + + pm_runtime_enable(dev); + + return 0; +} + +static int tegra186_dspk_platform_remove(struct platform_device *pdev) +{ + pm_runtime_disable(&pdev->dev); + + return 0; +} + +static const struct dev_pm_ops tegra186_dspk_pm_ops = { + SET_RUNTIME_PM_OPS(tegra186_dspk_runtime_suspend, + tegra186_dspk_runtime_resume, NULL) + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) +}; + +static struct platform_driver tegra186_dspk_driver = { + .driver = { + .name = "tegra186-dspk", + .of_match_table = tegra186_dspk_of_match, + .pm = &tegra186_dspk_pm_ops, + }, + .probe = tegra186_dspk_platform_probe, + .remove = tegra186_dspk_platform_remove, +}; +module_platform_driver(tegra186_dspk_driver); + +MODULE_AUTHOR("Mohan Kumar <mkumard@nvidia.com>"); +MODULE_AUTHOR("Sameer Pujar <spujar@nvidia.com>"); +MODULE_DESCRIPTION("Tegra186 ASoC DSPK driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/tegra/tegra186_dspk.h b/sound/soc/tegra/tegra186_dspk.h new file mode 100644 index 000000000..b2a879065 --- /dev/null +++ b/sound/soc/tegra/tegra186_dspk.h @@ -0,0 +1,70 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * tegra186_dspk.h - Definitions for Tegra186 DSPK driver + * + * Copyright (c) 2020 NVIDIA CORPORATION. All rights reserved. + * + */ + +#ifndef __TEGRA186_DSPK_H__ +#define __TEGRA186_DSPK_H__ + +/* Register offsets from DSPK BASE */ +#define TEGRA186_DSPK_RX_STATUS 0x0c +#define TEGRA186_DSPK_RX_INT_STATUS 0x10 +#define TEGRA186_DSPK_RX_INT_MASK 0x14 +#define TEGRA186_DSPK_RX_INT_SET 0x18 +#define TEGRA186_DSPK_RX_INT_CLEAR 0x1c +#define TEGRA186_DSPK_RX_CIF_CTRL 0x20 +#define TEGRA186_DSPK_ENABLE 0x40 +#define TEGRA186_DSPK_SOFT_RESET 0x44 +#define TEGRA186_DSPK_CG 0x48 +#define TEGRA186_DSPK_STATUS 0x4c +#define TEGRA186_DSPK_INT_STATUS 0x50 +#define TEGRA186_DSPK_CORE_CTRL 0x60 +#define TEGRA186_DSPK_CODEC_CTRL 0x64 + +/* DSPK CORE CONTROL fields */ +#define CH_SEL_SHIFT 8 +#define TEGRA186_DSPK_CHANNEL_SELECT_MASK (0x3 << CH_SEL_SHIFT) +#define DSPK_OSR_SHIFT 4 +#define TEGRA186_DSPK_OSR_MASK (0x3 << DSPK_OSR_SHIFT) +#define LRSEL_POL_SHIFT 0 +#define TEGRA186_DSPK_CTRL_LRSEL_POLARITY_MASK (0x1 << LRSEL_POL_SHIFT) +#define TEGRA186_DSPK_RX_FIFO_DEPTH 64 + +#define DSPK_OSR_FACTOR 32 + +/* DSPK interface clock ratio */ +#define DSPK_CLK_RATIO 4 + +enum tegra_dspk_osr { + DSPK_OSR_32, + DSPK_OSR_64, + DSPK_OSR_128, + DSPK_OSR_256, +}; + +enum tegra_dspk_ch_sel { + DSPK_CH_SELECT_LEFT, + DSPK_CH_SELECT_RIGHT, + DSPK_CH_SELECT_STEREO, +}; + +enum tegra_dspk_lrsel { + DSPK_LRSEL_LEFT, + DSPK_LRSEL_RIGHT, +}; + +struct tegra186_dspk { + unsigned int rx_fifo_th; + unsigned int osr_val; + unsigned int lrsel; + unsigned int ch_sel; + unsigned int mono_to_stereo; + unsigned int stereo_to_mono; + struct clk *clk_dspk; + struct regmap *regmap; +}; + +#endif diff --git a/sound/soc/tegra/tegra20_ac97.c b/sound/soc/tegra/tegra20_ac97.c new file mode 100644 index 000000000..06c728ae1 --- /dev/null +++ b/sound/soc/tegra/tegra20_ac97.c @@ -0,0 +1,441 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * tegra20_ac97.c - Tegra20 AC97 platform driver + * + * Copyright (c) 2012 Lucas Stach <dev@lynxeye.de> + * + * Partly based on code copyright/by: + * + * Copyright (c) 2011,2012 Toradex Inc. + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/gpio.h> +#include <linux/io.h> +#include <linux/jiffies.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_gpio.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 <sound/dmaengine_pcm.h> + +#include "tegra20_ac97.h" + +#define DRV_NAME "tegra20-ac97" + +static struct tegra20_ac97 *workdata; + +static void tegra20_ac97_codec_reset(struct snd_ac97 *ac97) +{ + u32 readback; + unsigned long timeout; + + /* reset line is not driven by DAC pad group, have to toggle GPIO */ + gpio_set_value(workdata->reset_gpio, 0); + udelay(2); + + gpio_set_value(workdata->reset_gpio, 1); + udelay(2); + + timeout = jiffies + msecs_to_jiffies(100); + + do { + regmap_read(workdata->regmap, TEGRA20_AC97_STATUS1, &readback); + if (readback & TEGRA20_AC97_STATUS1_CODEC1_RDY) + break; + usleep_range(1000, 2000); + } while (!time_after(jiffies, timeout)); +} + +static void tegra20_ac97_codec_warm_reset(struct snd_ac97 *ac97) +{ + u32 readback; + unsigned long timeout; + + /* + * although sync line is driven by the DAC pad group warm reset using + * the controller cmd is not working, have to toggle sync line + * manually. + */ + gpio_request(workdata->sync_gpio, "codec-sync"); + + gpio_direction_output(workdata->sync_gpio, 1); + + udelay(2); + gpio_set_value(workdata->sync_gpio, 0); + udelay(2); + gpio_free(workdata->sync_gpio); + + timeout = jiffies + msecs_to_jiffies(100); + + do { + regmap_read(workdata->regmap, TEGRA20_AC97_STATUS1, &readback); + if (readback & TEGRA20_AC97_STATUS1_CODEC1_RDY) + break; + usleep_range(1000, 2000); + } while (!time_after(jiffies, timeout)); +} + +static unsigned short tegra20_ac97_codec_read(struct snd_ac97 *ac97_snd, + unsigned short reg) +{ + u32 readback; + unsigned long timeout; + + regmap_write(workdata->regmap, TEGRA20_AC97_CMD, + (((reg | 0x80) << TEGRA20_AC97_CMD_CMD_ADDR_SHIFT) & + TEGRA20_AC97_CMD_CMD_ADDR_MASK) | + TEGRA20_AC97_CMD_BUSY); + + timeout = jiffies + msecs_to_jiffies(100); + + do { + regmap_read(workdata->regmap, TEGRA20_AC97_STATUS1, &readback); + if (readback & TEGRA20_AC97_STATUS1_STA_VALID1) + break; + usleep_range(1000, 2000); + } while (!time_after(jiffies, timeout)); + + return ((readback & TEGRA20_AC97_STATUS1_STA_DATA1_MASK) >> + TEGRA20_AC97_STATUS1_STA_DATA1_SHIFT); +} + +static void tegra20_ac97_codec_write(struct snd_ac97 *ac97_snd, + unsigned short reg, unsigned short val) +{ + u32 readback; + unsigned long timeout; + + regmap_write(workdata->regmap, TEGRA20_AC97_CMD, + ((reg << TEGRA20_AC97_CMD_CMD_ADDR_SHIFT) & + TEGRA20_AC97_CMD_CMD_ADDR_MASK) | + ((val << TEGRA20_AC97_CMD_CMD_DATA_SHIFT) & + TEGRA20_AC97_CMD_CMD_DATA_MASK) | + TEGRA20_AC97_CMD_BUSY); + + timeout = jiffies + msecs_to_jiffies(100); + + do { + regmap_read(workdata->regmap, TEGRA20_AC97_CMD, &readback); + if (!(readback & TEGRA20_AC97_CMD_BUSY)) + break; + usleep_range(1000, 2000); + } while (!time_after(jiffies, timeout)); +} + +static struct snd_ac97_bus_ops tegra20_ac97_ops = { + .read = tegra20_ac97_codec_read, + .write = tegra20_ac97_codec_write, + .reset = tegra20_ac97_codec_reset, + .warm_reset = tegra20_ac97_codec_warm_reset, +}; + +static inline void tegra20_ac97_start_playback(struct tegra20_ac97 *ac97) +{ + regmap_update_bits(ac97->regmap, TEGRA20_AC97_FIFO1_SCR, + TEGRA20_AC97_FIFO_SCR_PB_QRT_MT_EN, + TEGRA20_AC97_FIFO_SCR_PB_QRT_MT_EN); + + regmap_update_bits(ac97->regmap, TEGRA20_AC97_CTRL, + TEGRA20_AC97_CTRL_PCM_DAC_EN | + TEGRA20_AC97_CTRL_STM_EN, + TEGRA20_AC97_CTRL_PCM_DAC_EN | + TEGRA20_AC97_CTRL_STM_EN); +} + +static inline void tegra20_ac97_stop_playback(struct tegra20_ac97 *ac97) +{ + regmap_update_bits(ac97->regmap, TEGRA20_AC97_FIFO1_SCR, + TEGRA20_AC97_FIFO_SCR_PB_QRT_MT_EN, 0); + + regmap_update_bits(ac97->regmap, TEGRA20_AC97_CTRL, + TEGRA20_AC97_CTRL_PCM_DAC_EN, 0); +} + +static inline void tegra20_ac97_start_capture(struct tegra20_ac97 *ac97) +{ + regmap_update_bits(ac97->regmap, TEGRA20_AC97_FIFO1_SCR, + TEGRA20_AC97_FIFO_SCR_REC_FULL_EN, + TEGRA20_AC97_FIFO_SCR_REC_FULL_EN); +} + +static inline void tegra20_ac97_stop_capture(struct tegra20_ac97 *ac97) +{ + regmap_update_bits(ac97->regmap, TEGRA20_AC97_FIFO1_SCR, + TEGRA20_AC97_FIFO_SCR_REC_FULL_EN, 0); +} + +static int tegra20_ac97_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct tegra20_ac97 *ac97 = snd_soc_dai_get_drvdata(dai); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + case SNDRV_PCM_TRIGGER_RESUME: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + tegra20_ac97_start_playback(ac97); + else + tegra20_ac97_start_capture(ac97); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_SUSPEND: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + tegra20_ac97_stop_playback(ac97); + else + tegra20_ac97_stop_capture(ac97); + break; + default: + return -EINVAL; + } + + return 0; +} + +static const struct snd_soc_dai_ops tegra20_ac97_dai_ops = { + .trigger = tegra20_ac97_trigger, +}; + +static int tegra20_ac97_probe(struct snd_soc_dai *dai) +{ + struct tegra20_ac97 *ac97 = snd_soc_dai_get_drvdata(dai); + + dai->capture_dma_data = &ac97->capture_dma_data; + dai->playback_dma_data = &ac97->playback_dma_data; + + return 0; +} + +static struct snd_soc_dai_driver tegra20_ac97_dai = { + .name = "tegra-ac97-pcm", + .probe = tegra20_ac97_probe, + .playback = { + .stream_name = "PCM Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .stream_name = "PCM Capture", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &tegra20_ac97_dai_ops, +}; + +static const struct snd_soc_component_driver tegra20_ac97_component = { + .name = DRV_NAME, +}; + +static bool tegra20_ac97_wr_rd_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TEGRA20_AC97_CTRL: + case TEGRA20_AC97_CMD: + case TEGRA20_AC97_STATUS1: + case TEGRA20_AC97_FIFO1_SCR: + case TEGRA20_AC97_FIFO_TX1: + case TEGRA20_AC97_FIFO_RX1: + return true; + default: + break; + } + + return false; +} + +static bool tegra20_ac97_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TEGRA20_AC97_STATUS1: + case TEGRA20_AC97_FIFO1_SCR: + case TEGRA20_AC97_FIFO_TX1: + case TEGRA20_AC97_FIFO_RX1: + return true; + default: + break; + } + + return false; +} + +static bool tegra20_ac97_precious_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TEGRA20_AC97_FIFO_TX1: + case TEGRA20_AC97_FIFO_RX1: + return true; + default: + break; + } + + return false; +} + +static const struct regmap_config tegra20_ac97_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = TEGRA20_AC97_FIFO_RX1, + .writeable_reg = tegra20_ac97_wr_rd_reg, + .readable_reg = tegra20_ac97_wr_rd_reg, + .volatile_reg = tegra20_ac97_volatile_reg, + .precious_reg = tegra20_ac97_precious_reg, + .cache_type = REGCACHE_FLAT, +}; + +static int tegra20_ac97_platform_probe(struct platform_device *pdev) +{ + struct tegra20_ac97 *ac97; + struct resource *mem; + void __iomem *regs; + int ret = 0; + + ac97 = devm_kzalloc(&pdev->dev, sizeof(struct tegra20_ac97), + GFP_KERNEL); + if (!ac97) { + ret = -ENOMEM; + goto err; + } + dev_set_drvdata(&pdev->dev, ac97); + + ac97->clk_ac97 = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(ac97->clk_ac97)) { + dev_err(&pdev->dev, "Can't retrieve ac97 clock\n"); + ret = PTR_ERR(ac97->clk_ac97); + goto err; + } + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + regs = devm_ioremap_resource(&pdev->dev, mem); + if (IS_ERR(regs)) { + ret = PTR_ERR(regs); + goto err_clk_put; + } + + ac97->regmap = devm_regmap_init_mmio(&pdev->dev, regs, + &tegra20_ac97_regmap_config); + if (IS_ERR(ac97->regmap)) { + dev_err(&pdev->dev, "regmap init failed\n"); + ret = PTR_ERR(ac97->regmap); + goto err_clk_put; + } + + ac97->reset_gpio = of_get_named_gpio(pdev->dev.of_node, + "nvidia,codec-reset-gpio", 0); + if (gpio_is_valid(ac97->reset_gpio)) { + ret = devm_gpio_request_one(&pdev->dev, ac97->reset_gpio, + GPIOF_OUT_INIT_HIGH, "codec-reset"); + if (ret) { + dev_err(&pdev->dev, "could not get codec-reset GPIO\n"); + goto err_clk_put; + } + } else { + dev_err(&pdev->dev, "no codec-reset GPIO supplied\n"); + goto err_clk_put; + } + + ac97->sync_gpio = of_get_named_gpio(pdev->dev.of_node, + "nvidia,codec-sync-gpio", 0); + if (!gpio_is_valid(ac97->sync_gpio)) { + dev_err(&pdev->dev, "no codec-sync GPIO supplied\n"); + goto err_clk_put; + } + + ac97->capture_dma_data.addr = mem->start + TEGRA20_AC97_FIFO_RX1; + ac97->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + ac97->capture_dma_data.maxburst = 4; + + ac97->playback_dma_data.addr = mem->start + TEGRA20_AC97_FIFO_TX1; + ac97->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + ac97->playback_dma_data.maxburst = 4; + + ret = clk_prepare_enable(ac97->clk_ac97); + if (ret) { + dev_err(&pdev->dev, "clk_enable failed: %d\n", ret); + goto err_clk_put; + } + + ret = snd_soc_set_ac97_ops(&tegra20_ac97_ops); + if (ret) { + dev_err(&pdev->dev, "Failed to set AC'97 ops: %d\n", ret); + goto err_clk_disable_unprepare; + } + + ret = snd_soc_register_component(&pdev->dev, &tegra20_ac97_component, + &tegra20_ac97_dai, 1); + if (ret) { + dev_err(&pdev->dev, "Could not register DAI: %d\n", ret); + ret = -ENOMEM; + goto err_clk_disable_unprepare; + } + + ret = tegra_pcm_platform_register(&pdev->dev); + if (ret) { + dev_err(&pdev->dev, "Could not register PCM: %d\n", ret); + goto err_unregister_component; + } + + /* XXX: crufty ASoC AC97 API - only one AC97 codec allowed */ + workdata = ac97; + + return 0; + +err_unregister_component: + snd_soc_unregister_component(&pdev->dev); +err_clk_disable_unprepare: + clk_disable_unprepare(ac97->clk_ac97); +err_clk_put: +err: + snd_soc_set_ac97_ops(NULL); + return ret; +} + +static int tegra20_ac97_platform_remove(struct platform_device *pdev) +{ + struct tegra20_ac97 *ac97 = dev_get_drvdata(&pdev->dev); + + tegra_pcm_platform_unregister(&pdev->dev); + snd_soc_unregister_component(&pdev->dev); + + clk_disable_unprepare(ac97->clk_ac97); + + snd_soc_set_ac97_ops(NULL); + + return 0; +} + +static const struct of_device_id tegra20_ac97_of_match[] = { + { .compatible = "nvidia,tegra20-ac97", }, + {}, +}; + +static struct platform_driver tegra20_ac97_driver = { + .driver = { + .name = DRV_NAME, + .of_match_table = tegra20_ac97_of_match, + }, + .probe = tegra20_ac97_platform_probe, + .remove = tegra20_ac97_platform_remove, +}; +module_platform_driver(tegra20_ac97_driver); + +MODULE_AUTHOR("Lucas Stach"); +MODULE_DESCRIPTION("Tegra20 AC97 ASoC driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRV_NAME); +MODULE_DEVICE_TABLE(of, tegra20_ac97_of_match); diff --git a/sound/soc/tegra/tegra20_ac97.h b/sound/soc/tegra/tegra20_ac97.h new file mode 100644 index 000000000..e467cd1ff --- /dev/null +++ b/sound/soc/tegra/tegra20_ac97.h @@ -0,0 +1,85 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * tegra20_ac97.h - Definitions for the Tegra20 AC97 controller driver + * + * Copyright (c) 2012 Lucas Stach <dev@lynxeye.de> + * + * Partly based on code copyright/by: + * + * Copyright (c) 2011,2012 Toradex Inc. + */ + +#ifndef __TEGRA20_AC97_H__ +#define __TEGRA20_AC97_H__ + +#include "tegra_pcm.h" + +#define TEGRA20_AC97_CTRL 0x00 +#define TEGRA20_AC97_CMD 0x04 +#define TEGRA20_AC97_STATUS1 0x08 +/* ... */ +#define TEGRA20_AC97_FIFO1_SCR 0x1c +/* ... */ +#define TEGRA20_AC97_FIFO_TX1 0x40 +#define TEGRA20_AC97_FIFO_RX1 0x80 + +/* TEGRA20_AC97_CTRL */ +#define TEGRA20_AC97_CTRL_STM2_EN (1 << 16) +#define TEGRA20_AC97_CTRL_DOUBLE_SAMPLING_EN (1 << 11) +#define TEGRA20_AC97_CTRL_IO_CNTRL_EN (1 << 10) +#define TEGRA20_AC97_CTRL_HSET_DAC_EN (1 << 9) +#define TEGRA20_AC97_CTRL_LINE2_DAC_EN (1 << 8) +#define TEGRA20_AC97_CTRL_PCM_LFE_EN (1 << 7) +#define TEGRA20_AC97_CTRL_PCM_SUR_EN (1 << 6) +#define TEGRA20_AC97_CTRL_PCM_CEN_DAC_EN (1 << 5) +#define TEGRA20_AC97_CTRL_LINE1_DAC_EN (1 << 4) +#define TEGRA20_AC97_CTRL_PCM_DAC_EN (1 << 3) +#define TEGRA20_AC97_CTRL_COLD_RESET (1 << 2) +#define TEGRA20_AC97_CTRL_WARM_RESET (1 << 1) +#define TEGRA20_AC97_CTRL_STM_EN (1 << 0) + +/* TEGRA20_AC97_CMD */ +#define TEGRA20_AC97_CMD_CMD_ADDR_SHIFT 24 +#define TEGRA20_AC97_CMD_CMD_ADDR_MASK (0xff << TEGRA20_AC97_CMD_CMD_ADDR_SHIFT) +#define TEGRA20_AC97_CMD_CMD_DATA_SHIFT 8 +#define TEGRA20_AC97_CMD_CMD_DATA_MASK (0xffff << TEGRA20_AC97_CMD_CMD_DATA_SHIFT) +#define TEGRA20_AC97_CMD_CMD_ID_SHIFT 2 +#define TEGRA20_AC97_CMD_CMD_ID_MASK (0x3 << TEGRA20_AC97_CMD_CMD_ID_SHIFT) +#define TEGRA20_AC97_CMD_BUSY (1 << 0) + +/* TEGRA20_AC97_STATUS1 */ +#define TEGRA20_AC97_STATUS1_STA_ADDR1_SHIFT 24 +#define TEGRA20_AC97_STATUS1_STA_ADDR1_MASK (0xff << TEGRA20_AC97_STATUS1_STA_ADDR1_SHIFT) +#define TEGRA20_AC97_STATUS1_STA_DATA1_SHIFT 8 +#define TEGRA20_AC97_STATUS1_STA_DATA1_MASK (0xffff << TEGRA20_AC97_STATUS1_STA_DATA1_SHIFT) +#define TEGRA20_AC97_STATUS1_STA_VALID1 (1 << 2) +#define TEGRA20_AC97_STATUS1_STANDBY1 (1 << 1) +#define TEGRA20_AC97_STATUS1_CODEC1_RDY (1 << 0) + +/* TEGRA20_AC97_FIFO1_SCR */ +#define TEGRA20_AC97_FIFO_SCR_REC_MT_CNT_SHIFT 27 +#define TEGRA20_AC97_FIFO_SCR_REC_MT_CNT_MASK (0x1f << TEGRA20_AC97_FIFO_SCR_REC_MT_CNT_SHIFT) +#define TEGRA20_AC97_FIFO_SCR_PB_MT_CNT_SHIFT 22 +#define TEGRA20_AC97_FIFO_SCR_PB_MT_CNT_MASK (0x1f << TEGRA20_AC97_FIFO_SCR_PB_MT_CNT_SHIFT) +#define TEGRA20_AC97_FIFO_SCR_REC_OVERRUN_INT_STA (1 << 19) +#define TEGRA20_AC97_FIFO_SCR_PB_UNDERRUN_INT_STA (1 << 18) +#define TEGRA20_AC97_FIFO_SCR_REC_FORCE_MT (1 << 17) +#define TEGRA20_AC97_FIFO_SCR_PB_FORCE_MT (1 << 16) +#define TEGRA20_AC97_FIFO_SCR_REC_FULL_EN (1 << 15) +#define TEGRA20_AC97_FIFO_SCR_REC_3QRT_FULL_EN (1 << 14) +#define TEGRA20_AC97_FIFO_SCR_REC_QRT_FULL_EN (1 << 13) +#define TEGRA20_AC97_FIFO_SCR_REC_EMPTY_EN (1 << 12) +#define TEGRA20_AC97_FIFO_SCR_PB_NOT_FULL_EN (1 << 11) +#define TEGRA20_AC97_FIFO_SCR_PB_QRT_MT_EN (1 << 10) +#define TEGRA20_AC97_FIFO_SCR_PB_3QRT_MT_EN (1 << 9) +#define TEGRA20_AC97_FIFO_SCR_PB_EMPTY_MT_EN (1 << 8) + +struct tegra20_ac97 { + struct clk *clk_ac97; + struct snd_dmaengine_dai_dma_data capture_dma_data; + struct snd_dmaengine_dai_dma_data playback_dma_data; + struct regmap *regmap; + int reset_gpio; + int sync_gpio; +}; +#endif /* __TEGRA20_AC97_H__ */ diff --git a/sound/soc/tegra/tegra20_das.c b/sound/soc/tegra/tegra20_das.c new file mode 100644 index 000000000..79dba878d --- /dev/null +++ b/sound/soc/tegra/tegra20_das.c @@ -0,0 +1,213 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * tegra20_das.c - Tegra20 DAS driver + * + * Author: Stephen Warren <swarren@nvidia.com> + * Copyright (C) 2010 - NVIDIA, Inc. + */ + +#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 <= 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_FLAT, +}; + +static int tegra20_das_probe(struct platform_device *pdev) +{ + void __iomem *regs; + int ret = 0; + + if (das) + return -ENODEV; + + das = devm_kzalloc(&pdev->dev, sizeof(struct tegra20_das), GFP_KERNEL); + if (!das) { + ret = -ENOMEM; + goto err; + } + das->dev = &pdev->dev; + + regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(regs)) { + ret = PTR_ERR(regs); + 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; + } + + ret = tegra20_das_connect_dap_to_dac(TEGRA20_DAS_DAP_ID_3, + TEGRA20_DAS_DAP_SEL_DAC3); + 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_3, + TEGRA20_DAS_DAC_SEL_DAP3); + 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 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[] = { + { .compatible = "nvidia,tegra20-das", }, + {}, +}; + +static struct platform_driver tegra20_das_driver = { + .probe = tegra20_das_probe, + .remove = tegra20_das_remove, + .driver = { + .name = DRV_NAME, + .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 000000000..d22abc4d0 --- /dev/null +++ b/sound/soc/tegra/tegra20_das.h @@ -0,0 +1,120 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * tegra20_das.h - Definitions for Tegra20 DAS driver + * + * Author: Stephen Warren <swarren@nvidia.com> + * Copyright (C) 2010,2012 - NVIDIA, Inc. + */ + +#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 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 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 000000000..005fc4e64 --- /dev/null +++ b/sound/soc/tegra/tegra20_i2s.c @@ -0,0 +1,449 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * 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> + */ + +#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 <sound/dmaengine_pcm.h> + +#include "tegra20_i2s.h" + +#define DRV_NAME "tegra20-i2s" + +static int tegra20_i2s_runtime_suspend(struct device *dev) +{ + struct tegra20_i2s *i2s = dev_get_drvdata(dev); + + clk_disable_unprepare(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_prepare_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); + unsigned int mask = 0, val = 0; + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + default: + return -EINVAL; + } + + mask |= TEGRA20_I2S_CTRL_MASTER_ENABLE; + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + val |= TEGRA20_I2S_CTRL_MASTER_ENABLE; + break; + case SND_SOC_DAIFMT_CBM_CFM: + break; + default: + return -EINVAL; + } + + mask |= TEGRA20_I2S_CTRL_BIT_FORMAT_MASK | + TEGRA20_I2S_CTRL_LRCK_MASK; + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_A: + val |= TEGRA20_I2S_CTRL_BIT_FORMAT_DSP; + val |= TEGRA20_I2S_CTRL_LRCK_L_LOW; + break; + case SND_SOC_DAIFMT_DSP_B: + val |= TEGRA20_I2S_CTRL_BIT_FORMAT_DSP; + val |= TEGRA20_I2S_CTRL_LRCK_R_LOW; + break; + case SND_SOC_DAIFMT_I2S: + val |= TEGRA20_I2S_CTRL_BIT_FORMAT_I2S; + val |= TEGRA20_I2S_CTRL_LRCK_L_LOW; + break; + case SND_SOC_DAIFMT_RIGHT_J: + val |= TEGRA20_I2S_CTRL_BIT_FORMAT_RJM; + val |= TEGRA20_I2S_CTRL_LRCK_L_LOW; + break; + case SND_SOC_DAIFMT_LEFT_J: + val |= TEGRA20_I2S_CTRL_BIT_FORMAT_LJM; + val |= TEGRA20_I2S_CTRL_LRCK_L_LOW; + break; + default: + return -EINVAL; + } + + regmap_update_bits(i2s->regmap, TEGRA20_I2S_CTRL, mask, val); + + 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 = dai->dev; + struct tegra20_i2s *i2s = snd_soc_dai_get_drvdata(dai); + unsigned int mask, val; + int ret, sample_size, srate, i2sclock, bitcnt; + + mask = TEGRA20_I2S_CTRL_BIT_SIZE_MASK; + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + val = TEGRA20_I2S_CTRL_BIT_SIZE_16; + sample_size = 16; + break; + case SNDRV_PCM_FORMAT_S24_LE: + val = TEGRA20_I2S_CTRL_BIT_SIZE_24; + sample_size = 24; + break; + case SNDRV_PCM_FORMAT_S32_LE: + val = TEGRA20_I2S_CTRL_BIT_SIZE_32; + sample_size = 32; + break; + default: + return -EINVAL; + } + + mask |= TEGRA20_I2S_CTRL_FIFO_FORMAT_MASK; + val |= TEGRA20_I2S_CTRL_FIFO_FORMAT_PACKED; + + regmap_update_bits(i2s->regmap, TEGRA20_I2S_CTRL, mask, val); + + 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; + val = bitcnt << TEGRA20_I2S_TIMING_CHANNEL_BIT_COUNT_SHIFT; + + if (i2sclock % (2 * srate)) + val |= TEGRA20_I2S_TIMING_NON_SYM_ENABLE; + + regmap_write(i2s->regmap, TEGRA20_I2S_TIMING, val); + + regmap_write(i2s->regmap, 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) +{ + regmap_update_bits(i2s->regmap, TEGRA20_I2S_CTRL, + TEGRA20_I2S_CTRL_FIFO1_ENABLE, + TEGRA20_I2S_CTRL_FIFO1_ENABLE); +} + +static void tegra20_i2s_stop_playback(struct tegra20_i2s *i2s) +{ + regmap_update_bits(i2s->regmap, TEGRA20_I2S_CTRL, + TEGRA20_I2S_CTRL_FIFO1_ENABLE, 0); +} + +static void tegra20_i2s_start_capture(struct tegra20_i2s *i2s) +{ + regmap_update_bits(i2s->regmap, TEGRA20_I2S_CTRL, + TEGRA20_I2S_CTRL_FIFO2_ENABLE, + TEGRA20_I2S_CTRL_FIFO2_ENABLE); +} + +static void tegra20_i2s_stop_capture(struct tegra20_i2s *i2s) +{ + regmap_update_bits(i2s->regmap, TEGRA20_I2S_CTRL, + TEGRA20_I2S_CTRL_FIFO2_ENABLE, 0); +} + +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 = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .stream_name = "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 const struct snd_soc_component_driver tegra20_i2s_component = { + .name = DRV_NAME, +}; + +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_FLAT, +}; + +static int tegra20_i2s_platform_probe(struct platform_device *pdev) +{ + struct tegra20_i2s *i2s; + struct resource *mem; + void __iomem *regs; + int ret; + + i2s = devm_kzalloc(&pdev->dev, sizeof(struct tegra20_i2s), GFP_KERNEL); + if (!i2s) { + 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); + regs = devm_ioremap_resource(&pdev->dev, mem); + if (IS_ERR(regs)) { + ret = PTR_ERR(regs); + 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.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + i2s->capture_dma_data.maxburst = 4; + + i2s->playback_dma_data.addr = mem->start + TEGRA20_I2S_FIFO1; + i2s->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + i2s->playback_dma_data.maxburst = 4; + + 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_component(&pdev->dev, &tegra20_i2s_component, + &i2s->dai, 1); + 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_component; + } + + return 0; + +err_unregister_component: + snd_soc_unregister_component(&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 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_component(&pdev->dev); + + clk_put(i2s->clk_i2s); + + return 0; +} + +static const struct of_device_id tegra20_i2s_of_match[] = { + { .compatible = "nvidia,tegra20-i2s", }, + {}, +}; + +static const struct dev_pm_ops tegra20_i2s_pm_ops = { + SET_RUNTIME_PM_OPS(tegra20_i2s_runtime_suspend, + tegra20_i2s_runtime_resume, NULL) +}; + +static struct platform_driver tegra20_i2s_driver = { + .driver = { + .name = DRV_NAME, + .of_match_table = tegra20_i2s_of_match, + .pm = &tegra20_i2s_pm_ops, + }, + .probe = tegra20_i2s_platform_probe, + .remove = 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 000000000..628d3ca09 --- /dev/null +++ b/sound/soc/tegra/tegra20_i2s.h @@ -0,0 +1,149 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * 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> + */ + +#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 0x7ff +#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 snd_dmaengine_dai_dma_data capture_dma_data; + struct snd_dmaengine_dai_dma_data playback_dma_data; + struct regmap *regmap; +}; + +#endif diff --git a/sound/soc/tegra/tegra20_spdif.c b/sound/soc/tegra/tegra20_spdif.c new file mode 100644 index 000000000..5839833e2 --- /dev/null +++ b/sound/soc/tegra/tegra20_spdif.c @@ -0,0 +1,361 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * tegra20_spdif.c - Tegra20 SPDIF driver + * + * Author: Stephen Warren <swarren@nvidia.com> + * Copyright (C) 2011-2012 - NVIDIA, Inc. + */ + +#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 <sound/dmaengine_pcm.h> + +#include "tegra20_spdif.h" + +#define DRV_NAME "tegra20-spdif" + +static int tegra20_spdif_runtime_suspend(struct device *dev) +{ + struct tegra20_spdif *spdif = dev_get_drvdata(dev); + + clk_disable_unprepare(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_prepare_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 = dai->dev; + struct tegra20_spdif *spdif = snd_soc_dai_get_drvdata(dai); + unsigned int mask = 0, val = 0; + int ret, spdifclock; + + mask |= TEGRA20_SPDIF_CTRL_PACK | + TEGRA20_SPDIF_CTRL_BIT_MODE_MASK; + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + val |= TEGRA20_SPDIF_CTRL_PACK | + TEGRA20_SPDIF_CTRL_BIT_MODE_16BIT; + break; + default: + return -EINVAL; + } + + regmap_update_bits(spdif->regmap, TEGRA20_SPDIF_CTRL, mask, val); + + 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) +{ + regmap_update_bits(spdif->regmap, TEGRA20_SPDIF_CTRL, + TEGRA20_SPDIF_CTRL_TX_EN, + TEGRA20_SPDIF_CTRL_TX_EN); +} + +static void tegra20_spdif_stop_playback(struct tegra20_spdif *spdif) +{ + regmap_update_bits(spdif->regmap, TEGRA20_SPDIF_CTRL, + TEGRA20_SPDIF_CTRL_TX_EN, 0); +} + +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 = { + .stream_name = "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 const struct snd_soc_component_driver tegra20_spdif_component = { + .name = DRV_NAME, +}; + +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_FLAT, +}; + +static int tegra20_spdif_platform_probe(struct platform_device *pdev) +{ + struct tegra20_spdif *spdif; + struct resource *mem, *dmareq; + void __iomem *regs; + int ret; + + spdif = devm_kzalloc(&pdev->dev, sizeof(struct tegra20_spdif), + GFP_KERNEL); + if (!spdif) + return -ENOMEM; + + dev_set_drvdata(&pdev->dev, spdif); + + spdif->clk_spdif_out = devm_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); + return ret; + } + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + regs = devm_ioremap_resource(&pdev->dev, mem); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + dmareq = platform_get_resource(pdev, IORESOURCE_DMA, 0); + if (!dmareq) { + dev_err(&pdev->dev, "No DMA resource\n"); + return -ENODEV; + } + + 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); + return ret; + } + + spdif->playback_dma_data.addr = mem->start + TEGRA20_SPDIF_DATA_OUT; + spdif->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + spdif->playback_dma_data.maxburst = 4; + spdif->playback_dma_data.slave_id = 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_component(&pdev->dev, &tegra20_spdif_component, + &tegra20_spdif_dai, 1); + 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_component; + } + + return 0; + +err_unregister_component: + snd_soc_unregister_component(&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); + + return ret; +} + +static int tegra20_spdif_platform_remove(struct platform_device *pdev) +{ + 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_component(&pdev->dev); + + return 0; +} + +static const struct dev_pm_ops tegra20_spdif_pm_ops = { + SET_RUNTIME_PM_OPS(tegra20_spdif_runtime_suspend, + tegra20_spdif_runtime_resume, NULL) +}; + +static struct platform_driver tegra20_spdif_driver = { + .driver = { + .name = DRV_NAME, + .pm = &tegra20_spdif_pm_ops, + }, + .probe = tegra20_spdif_platform_probe, + .remove = 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 000000000..1973ffc2d --- /dev/null +++ b/sound/soc/tegra/tegra20_spdif.h @@ -0,0 +1,456 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * 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 + */ + +#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 snd_dmaengine_dai_dma_data capture_dma_data; + struct snd_dmaengine_dai_dma_data playback_dma_data; + struct regmap *regmap; +}; + +#endif diff --git a/sound/soc/tegra/tegra210_admaif.c b/sound/soc/tegra/tegra210_admaif.c new file mode 100644 index 000000000..d610cbe4a --- /dev/null +++ b/sound/soc/tegra/tegra210_admaif.c @@ -0,0 +1,880 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// tegra210_admaif.c - Tegra ADMAIF driver +// +// Copyright (c) 2020 NVIDIA CORPORATION. All rights reserved. + +#include <linux/clk.h> +#include <linux/device.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 <sound/pcm_params.h> +#include <sound/soc.h> +#include "tegra210_admaif.h" +#include "tegra_cif.h" +#include "tegra_pcm.h" + +#define CH_REG(offset, reg, id) \ + ((offset) + (reg) + (TEGRA_ADMAIF_CHANNEL_REG_STRIDE * (id))) + +#define CH_TX_REG(reg, id) CH_REG(admaif->soc_data->tx_base, reg, id) + +#define CH_RX_REG(reg, id) CH_REG(admaif->soc_data->rx_base, reg, id) + +#define REG_DEFAULTS(id, rx_ctrl, tx_ctrl, tx_base, rx_base) \ + { CH_REG(rx_base, TEGRA_ADMAIF_RX_INT_MASK, id), 0x00000001 }, \ + { CH_REG(rx_base, TEGRA_ADMAIF_CH_ACIF_RX_CTRL, id), 0x00007700 }, \ + { CH_REG(rx_base, TEGRA_ADMAIF_RX_FIFO_CTRL, id), rx_ctrl }, \ + { CH_REG(tx_base, TEGRA_ADMAIF_TX_INT_MASK, id), 0x00000001 }, \ + { CH_REG(tx_base, TEGRA_ADMAIF_CH_ACIF_TX_CTRL, id), 0x00007700 }, \ + { CH_REG(tx_base, TEGRA_ADMAIF_TX_FIFO_CTRL, id), tx_ctrl } + +#define ADMAIF_REG_DEFAULTS(id, chip) \ + REG_DEFAULTS((id) - 1, \ + chip ## _ADMAIF_RX ## id ## _FIFO_CTRL_REG_DEFAULT, \ + chip ## _ADMAIF_TX ## id ## _FIFO_CTRL_REG_DEFAULT, \ + chip ## _ADMAIF_TX_BASE, \ + chip ## _ADMAIF_RX_BASE) + +static const struct reg_default tegra186_admaif_reg_defaults[] = { + {(TEGRA_ADMAIF_GLOBAL_CG_0 + TEGRA186_ADMAIF_GLOBAL_BASE), 0x00000003}, + ADMAIF_REG_DEFAULTS(1, TEGRA186), + ADMAIF_REG_DEFAULTS(2, TEGRA186), + ADMAIF_REG_DEFAULTS(3, TEGRA186), + ADMAIF_REG_DEFAULTS(4, TEGRA186), + ADMAIF_REG_DEFAULTS(5, TEGRA186), + ADMAIF_REG_DEFAULTS(6, TEGRA186), + ADMAIF_REG_DEFAULTS(7, TEGRA186), + ADMAIF_REG_DEFAULTS(8, TEGRA186), + ADMAIF_REG_DEFAULTS(9, TEGRA186), + ADMAIF_REG_DEFAULTS(10, TEGRA186), + ADMAIF_REG_DEFAULTS(11, TEGRA186), + ADMAIF_REG_DEFAULTS(12, TEGRA186), + ADMAIF_REG_DEFAULTS(13, TEGRA186), + ADMAIF_REG_DEFAULTS(14, TEGRA186), + ADMAIF_REG_DEFAULTS(15, TEGRA186), + ADMAIF_REG_DEFAULTS(16, TEGRA186), + ADMAIF_REG_DEFAULTS(17, TEGRA186), + ADMAIF_REG_DEFAULTS(18, TEGRA186), + ADMAIF_REG_DEFAULTS(19, TEGRA186), + ADMAIF_REG_DEFAULTS(20, TEGRA186) +}; + +static const struct reg_default tegra210_admaif_reg_defaults[] = { + {(TEGRA_ADMAIF_GLOBAL_CG_0 + TEGRA210_ADMAIF_GLOBAL_BASE), 0x00000003}, + ADMAIF_REG_DEFAULTS(1, TEGRA210), + ADMAIF_REG_DEFAULTS(2, TEGRA210), + ADMAIF_REG_DEFAULTS(3, TEGRA210), + ADMAIF_REG_DEFAULTS(4, TEGRA210), + ADMAIF_REG_DEFAULTS(5, TEGRA210), + ADMAIF_REG_DEFAULTS(6, TEGRA210), + ADMAIF_REG_DEFAULTS(7, TEGRA210), + ADMAIF_REG_DEFAULTS(8, TEGRA210), + ADMAIF_REG_DEFAULTS(9, TEGRA210), + ADMAIF_REG_DEFAULTS(10, TEGRA210) +}; + +static bool tegra_admaif_wr_reg(struct device *dev, unsigned int reg) +{ + struct tegra_admaif *admaif = dev_get_drvdata(dev); + unsigned int ch_stride = TEGRA_ADMAIF_CHANNEL_REG_STRIDE; + unsigned int num_ch = admaif->soc_data->num_ch; + unsigned int rx_base = admaif->soc_data->rx_base; + unsigned int tx_base = admaif->soc_data->tx_base; + unsigned int global_base = admaif->soc_data->global_base; + unsigned int reg_max = admaif->soc_data->regmap_conf->max_register; + unsigned int rx_max = rx_base + (num_ch * ch_stride); + unsigned int tx_max = tx_base + (num_ch * ch_stride); + + if ((reg >= rx_base) && (reg < rx_max)) { + reg = (reg - rx_base) % ch_stride; + if ((reg == TEGRA_ADMAIF_RX_ENABLE) || + (reg == TEGRA_ADMAIF_RX_FIFO_CTRL) || + (reg == TEGRA_ADMAIF_RX_SOFT_RESET) || + (reg == TEGRA_ADMAIF_CH_ACIF_RX_CTRL)) + return true; + } else if ((reg >= tx_base) && (reg < tx_max)) { + reg = (reg - tx_base) % ch_stride; + if ((reg == TEGRA_ADMAIF_TX_ENABLE) || + (reg == TEGRA_ADMAIF_TX_FIFO_CTRL) || + (reg == TEGRA_ADMAIF_TX_SOFT_RESET) || + (reg == TEGRA_ADMAIF_CH_ACIF_TX_CTRL)) + return true; + } else if ((reg >= global_base) && (reg < reg_max)) { + if (reg == (global_base + TEGRA_ADMAIF_GLOBAL_ENABLE)) + return true; + } + + return false; +} + +static bool tegra_admaif_rd_reg(struct device *dev, unsigned int reg) +{ + struct tegra_admaif *admaif = dev_get_drvdata(dev); + unsigned int ch_stride = TEGRA_ADMAIF_CHANNEL_REG_STRIDE; + unsigned int num_ch = admaif->soc_data->num_ch; + unsigned int rx_base = admaif->soc_data->rx_base; + unsigned int tx_base = admaif->soc_data->tx_base; + unsigned int global_base = admaif->soc_data->global_base; + unsigned int reg_max = admaif->soc_data->regmap_conf->max_register; + unsigned int rx_max = rx_base + (num_ch * ch_stride); + unsigned int tx_max = tx_base + (num_ch * ch_stride); + + if ((reg >= rx_base) && (reg < rx_max)) { + reg = (reg - rx_base) % ch_stride; + if ((reg == TEGRA_ADMAIF_RX_ENABLE) || + (reg == TEGRA_ADMAIF_RX_STATUS) || + (reg == TEGRA_ADMAIF_RX_INT_STATUS) || + (reg == TEGRA_ADMAIF_RX_FIFO_CTRL) || + (reg == TEGRA_ADMAIF_RX_SOFT_RESET) || + (reg == TEGRA_ADMAIF_CH_ACIF_RX_CTRL)) + return true; + } else if ((reg >= tx_base) && (reg < tx_max)) { + reg = (reg - tx_base) % ch_stride; + if ((reg == TEGRA_ADMAIF_TX_ENABLE) || + (reg == TEGRA_ADMAIF_TX_STATUS) || + (reg == TEGRA_ADMAIF_TX_INT_STATUS) || + (reg == TEGRA_ADMAIF_TX_FIFO_CTRL) || + (reg == TEGRA_ADMAIF_TX_SOFT_RESET) || + (reg == TEGRA_ADMAIF_CH_ACIF_TX_CTRL)) + return true; + } else if ((reg >= global_base) && (reg < reg_max)) { + if ((reg == (global_base + TEGRA_ADMAIF_GLOBAL_ENABLE)) || + (reg == (global_base + TEGRA_ADMAIF_GLOBAL_CG_0)) || + (reg == (global_base + TEGRA_ADMAIF_GLOBAL_STATUS)) || + (reg == (global_base + + TEGRA_ADMAIF_GLOBAL_RX_ENABLE_STATUS)) || + (reg == (global_base + + TEGRA_ADMAIF_GLOBAL_TX_ENABLE_STATUS))) + return true; + } + + return false; +} + +static bool tegra_admaif_volatile_reg(struct device *dev, unsigned int reg) +{ + struct tegra_admaif *admaif = dev_get_drvdata(dev); + unsigned int ch_stride = TEGRA_ADMAIF_CHANNEL_REG_STRIDE; + unsigned int num_ch = admaif->soc_data->num_ch; + unsigned int rx_base = admaif->soc_data->rx_base; + unsigned int tx_base = admaif->soc_data->tx_base; + unsigned int global_base = admaif->soc_data->global_base; + unsigned int reg_max = admaif->soc_data->regmap_conf->max_register; + unsigned int rx_max = rx_base + (num_ch * ch_stride); + unsigned int tx_max = tx_base + (num_ch * ch_stride); + + if ((reg >= rx_base) && (reg < rx_max)) { + reg = (reg - rx_base) % ch_stride; + if ((reg == TEGRA_ADMAIF_RX_ENABLE) || + (reg == TEGRA_ADMAIF_RX_STATUS) || + (reg == TEGRA_ADMAIF_RX_INT_STATUS) || + (reg == TEGRA_ADMAIF_RX_SOFT_RESET)) + return true; + } else if ((reg >= tx_base) && (reg < tx_max)) { + reg = (reg - tx_base) % ch_stride; + if ((reg == TEGRA_ADMAIF_TX_ENABLE) || + (reg == TEGRA_ADMAIF_TX_STATUS) || + (reg == TEGRA_ADMAIF_TX_INT_STATUS) || + (reg == TEGRA_ADMAIF_TX_SOFT_RESET)) + return true; + } else if ((reg >= global_base) && (reg < reg_max)) { + if ((reg == (global_base + TEGRA_ADMAIF_GLOBAL_STATUS)) || + (reg == (global_base + + TEGRA_ADMAIF_GLOBAL_RX_ENABLE_STATUS)) || + (reg == (global_base + + TEGRA_ADMAIF_GLOBAL_TX_ENABLE_STATUS))) + return true; + } + + return false; +} + +static const struct regmap_config tegra210_admaif_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = TEGRA210_ADMAIF_LAST_REG, + .writeable_reg = tegra_admaif_wr_reg, + .readable_reg = tegra_admaif_rd_reg, + .volatile_reg = tegra_admaif_volatile_reg, + .reg_defaults = tegra210_admaif_reg_defaults, + .num_reg_defaults = TEGRA210_ADMAIF_CHANNEL_COUNT * 6 + 1, + .cache_type = REGCACHE_FLAT, +}; + +static const struct regmap_config tegra186_admaif_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = TEGRA186_ADMAIF_LAST_REG, + .writeable_reg = tegra_admaif_wr_reg, + .readable_reg = tegra_admaif_rd_reg, + .volatile_reg = tegra_admaif_volatile_reg, + .reg_defaults = tegra186_admaif_reg_defaults, + .num_reg_defaults = TEGRA186_ADMAIF_CHANNEL_COUNT * 6 + 1, + .cache_type = REGCACHE_FLAT, +}; + +static int __maybe_unused tegra_admaif_runtime_suspend(struct device *dev) +{ + struct tegra_admaif *admaif = dev_get_drvdata(dev); + + regcache_cache_only(admaif->regmap, true); + regcache_mark_dirty(admaif->regmap); + + return 0; +} + +static int __maybe_unused tegra_admaif_runtime_resume(struct device *dev) +{ + struct tegra_admaif *admaif = dev_get_drvdata(dev); + + regcache_cache_only(admaif->regmap, false); + regcache_sync(admaif->regmap); + + return 0; +} + +static int tegra_admaif_set_pack_mode(struct regmap *map, unsigned int reg, + int valid_bit) +{ + switch (valid_bit) { + case DATA_8BIT: + regmap_update_bits(map, reg, PACK8_EN_MASK, PACK8_EN); + regmap_update_bits(map, reg, PACK16_EN_MASK, 0); + break; + case DATA_16BIT: + regmap_update_bits(map, reg, PACK16_EN_MASK, PACK16_EN); + regmap_update_bits(map, reg, PACK8_EN_MASK, 0); + break; + case DATA_32BIT: + regmap_update_bits(map, reg, PACK16_EN_MASK, 0); + regmap_update_bits(map, reg, PACK8_EN_MASK, 0); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int tegra_admaif_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct device *dev = dai->dev; + struct tegra_admaif *admaif = snd_soc_dai_get_drvdata(dai); + struct tegra_cif_conf cif_conf; + unsigned int reg, path; + int valid_bit, channels; + + memset(&cif_conf, 0, sizeof(struct tegra_cif_conf)); + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S8: + cif_conf.audio_bits = TEGRA_ACIF_BITS_8; + cif_conf.client_bits = TEGRA_ACIF_BITS_8; + valid_bit = DATA_8BIT; + break; + case SNDRV_PCM_FORMAT_S16_LE: + cif_conf.audio_bits = TEGRA_ACIF_BITS_16; + cif_conf.client_bits = TEGRA_ACIF_BITS_16; + valid_bit = DATA_16BIT; + break; + case SNDRV_PCM_FORMAT_S32_LE: + cif_conf.audio_bits = TEGRA_ACIF_BITS_32; + cif_conf.client_bits = TEGRA_ACIF_BITS_32; + valid_bit = DATA_32BIT; + break; + default: + dev_err(dev, "unsupported format!\n"); + return -EOPNOTSUPP; + } + + channels = params_channels(params); + cif_conf.client_ch = channels; + cif_conf.audio_ch = channels; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + path = ADMAIF_TX_PATH; + reg = CH_TX_REG(TEGRA_ADMAIF_CH_ACIF_TX_CTRL, dai->id); + } else { + path = ADMAIF_RX_PATH; + reg = CH_RX_REG(TEGRA_ADMAIF_CH_ACIF_RX_CTRL, dai->id); + } + + cif_conf.mono_conv = admaif->mono_to_stereo[path][dai->id]; + cif_conf.stereo_conv = admaif->stereo_to_mono[path][dai->id]; + + tegra_admaif_set_pack_mode(admaif->regmap, reg, valid_bit); + + tegra_set_cif(admaif->regmap, reg, &cif_conf); + + return 0; +} + +static int tegra_admaif_start(struct snd_soc_dai *dai, int direction) +{ + struct tegra_admaif *admaif = snd_soc_dai_get_drvdata(dai); + unsigned int reg, mask, val; + + switch (direction) { + case SNDRV_PCM_STREAM_PLAYBACK: + mask = TX_ENABLE_MASK; + val = TX_ENABLE; + reg = CH_TX_REG(TEGRA_ADMAIF_TX_ENABLE, dai->id); + break; + case SNDRV_PCM_STREAM_CAPTURE: + mask = RX_ENABLE_MASK; + val = RX_ENABLE; + reg = CH_RX_REG(TEGRA_ADMAIF_RX_ENABLE, dai->id); + break; + default: + return -EINVAL; + } + + regmap_update_bits(admaif->regmap, reg, mask, val); + + return 0; +} + +static int tegra_admaif_stop(struct snd_soc_dai *dai, int direction) +{ + struct tegra_admaif *admaif = snd_soc_dai_get_drvdata(dai); + unsigned int enable_reg, status_reg, reset_reg, mask, val; + char *dir_name; + int err, enable; + + switch (direction) { + case SNDRV_PCM_STREAM_PLAYBACK: + mask = TX_ENABLE_MASK; + enable = TX_ENABLE; + dir_name = "TX"; + enable_reg = CH_TX_REG(TEGRA_ADMAIF_TX_ENABLE, dai->id); + status_reg = CH_TX_REG(TEGRA_ADMAIF_TX_STATUS, dai->id); + reset_reg = CH_TX_REG(TEGRA_ADMAIF_TX_SOFT_RESET, dai->id); + break; + case SNDRV_PCM_STREAM_CAPTURE: + mask = RX_ENABLE_MASK; + enable = RX_ENABLE; + dir_name = "RX"; + enable_reg = CH_RX_REG(TEGRA_ADMAIF_RX_ENABLE, dai->id); + status_reg = CH_RX_REG(TEGRA_ADMAIF_RX_STATUS, dai->id); + reset_reg = CH_RX_REG(TEGRA_ADMAIF_RX_SOFT_RESET, dai->id); + break; + default: + return -EINVAL; + } + + /* Disable TX/RX channel */ + regmap_update_bits(admaif->regmap, enable_reg, mask, ~enable); + + /* Wait until ADMAIF TX/RX status is disabled */ + err = regmap_read_poll_timeout_atomic(admaif->regmap, status_reg, val, + !(val & enable), 10, 10000); + if (err < 0) + dev_warn(dai->dev, "timeout: failed to disable ADMAIF%d_%s\n", + dai->id + 1, dir_name); + + /* SW reset */ + regmap_update_bits(admaif->regmap, reset_reg, SW_RESET_MASK, SW_RESET); + + /* Wait till SW reset is complete */ + err = regmap_read_poll_timeout_atomic(admaif->regmap, reset_reg, val, + !(val & SW_RESET_MASK & SW_RESET), + 10, 10000); + if (err) { + dev_err(dai->dev, "timeout: SW reset failed for ADMAIF%d_%s\n", + dai->id + 1, dir_name); + return err; + } + + return 0; +} + +static int tegra_admaif_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + int err; + + err = snd_dmaengine_pcm_trigger(substream, cmd); + if (err) + return err; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + case SNDRV_PCM_TRIGGER_RESUME: + return tegra_admaif_start(dai, substream->stream); + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_SUSPEND: + return tegra_admaif_stop(dai, substream->stream); + default: + return -EINVAL; + } +} + +static const struct snd_soc_dai_ops tegra_admaif_dai_ops = { + .hw_params = tegra_admaif_hw_params, + .trigger = tegra_admaif_trigger, +}; + +static int tegra210_admaif_pget_mono_to_stereo(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); + struct tegra_admaif *admaif = snd_soc_component_get_drvdata(cmpnt); + struct soc_enum *ec = (struct soc_enum *)kcontrol->private_value; + + ucontrol->value.enumerated.item[0] = + admaif->mono_to_stereo[ADMAIF_TX_PATH][ec->reg]; + + return 0; +} + +static int tegra210_admaif_pput_mono_to_stereo(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); + struct tegra_admaif *admaif = snd_soc_component_get_drvdata(cmpnt); + struct soc_enum *ec = (struct soc_enum *)kcontrol->private_value; + unsigned int value = ucontrol->value.enumerated.item[0]; + + if (value == admaif->mono_to_stereo[ADMAIF_TX_PATH][ec->reg]) + return 0; + + admaif->mono_to_stereo[ADMAIF_TX_PATH][ec->reg] = value; + + return 1; +} + +static int tegra210_admaif_cget_mono_to_stereo(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); + struct tegra_admaif *admaif = snd_soc_component_get_drvdata(cmpnt); + struct soc_enum *ec = (struct soc_enum *)kcontrol->private_value; + + ucontrol->value.enumerated.item[0] = + admaif->mono_to_stereo[ADMAIF_RX_PATH][ec->reg]; + + return 0; +} + +static int tegra210_admaif_cput_mono_to_stereo(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); + struct tegra_admaif *admaif = snd_soc_component_get_drvdata(cmpnt); + struct soc_enum *ec = (struct soc_enum *)kcontrol->private_value; + unsigned int value = ucontrol->value.enumerated.item[0]; + + if (value == admaif->mono_to_stereo[ADMAIF_RX_PATH][ec->reg]) + return 0; + + admaif->mono_to_stereo[ADMAIF_RX_PATH][ec->reg] = value; + + return 1; +} + +static int tegra210_admaif_pget_stereo_to_mono(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); + struct tegra_admaif *admaif = snd_soc_component_get_drvdata(cmpnt); + struct soc_enum *ec = (struct soc_enum *)kcontrol->private_value; + + ucontrol->value.enumerated.item[0] = + admaif->stereo_to_mono[ADMAIF_TX_PATH][ec->reg]; + + return 0; +} + +static int tegra210_admaif_pput_stereo_to_mono(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); + struct tegra_admaif *admaif = snd_soc_component_get_drvdata(cmpnt); + struct soc_enum *ec = (struct soc_enum *)kcontrol->private_value; + unsigned int value = ucontrol->value.enumerated.item[0]; + + if (value == admaif->stereo_to_mono[ADMAIF_TX_PATH][ec->reg]) + return 0; + + admaif->stereo_to_mono[ADMAIF_TX_PATH][ec->reg] = value; + + return 1; +} + +static int tegra210_admaif_cget_stereo_to_mono(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); + struct tegra_admaif *admaif = snd_soc_component_get_drvdata(cmpnt); + struct soc_enum *ec = (struct soc_enum *)kcontrol->private_value; + + ucontrol->value.enumerated.item[0] = + admaif->stereo_to_mono[ADMAIF_RX_PATH][ec->reg]; + + return 0; +} + +static int tegra210_admaif_cput_stereo_to_mono(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); + struct tegra_admaif *admaif = snd_soc_component_get_drvdata(cmpnt); + struct soc_enum *ec = (struct soc_enum *)kcontrol->private_value; + unsigned int value = ucontrol->value.enumerated.item[0]; + + if (value == admaif->stereo_to_mono[ADMAIF_RX_PATH][ec->reg]) + return 0; + + admaif->stereo_to_mono[ADMAIF_RX_PATH][ec->reg] = value; + + return 1; +} + +static int tegra_admaif_dai_probe(struct snd_soc_dai *dai) +{ + struct tegra_admaif *admaif = snd_soc_dai_get_drvdata(dai); + + dai->capture_dma_data = &admaif->capture_dma_data[dai->id]; + dai->playback_dma_data = &admaif->playback_dma_data[dai->id]; + + return 0; +} + +#define DAI(dai_name) \ + { \ + .name = dai_name, \ + .probe = tegra_admaif_dai_probe, \ + .playback = { \ + .stream_name = dai_name " Playback", \ + .channels_min = 1, \ + .channels_max = 16, \ + .rates = SNDRV_PCM_RATE_8000_192000, \ + .formats = SNDRV_PCM_FMTBIT_S8 | \ + SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S32_LE, \ + }, \ + .capture = { \ + .stream_name = dai_name " Capture", \ + .channels_min = 1, \ + .channels_max = 16, \ + .rates = SNDRV_PCM_RATE_8000_192000, \ + .formats = SNDRV_PCM_FMTBIT_S8 | \ + SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S32_LE, \ + }, \ + .ops = &tegra_admaif_dai_ops, \ + } + +static struct snd_soc_dai_driver tegra210_admaif_cmpnt_dais[] = { + DAI("ADMAIF1"), + DAI("ADMAIF2"), + DAI("ADMAIF3"), + DAI("ADMAIF4"), + DAI("ADMAIF5"), + DAI("ADMAIF6"), + DAI("ADMAIF7"), + DAI("ADMAIF8"), + DAI("ADMAIF9"), + DAI("ADMAIF10"), +}; + +static struct snd_soc_dai_driver tegra186_admaif_cmpnt_dais[] = { + DAI("ADMAIF1"), + DAI("ADMAIF2"), + DAI("ADMAIF3"), + DAI("ADMAIF4"), + DAI("ADMAIF5"), + DAI("ADMAIF6"), + DAI("ADMAIF7"), + DAI("ADMAIF8"), + DAI("ADMAIF9"), + DAI("ADMAIF10"), + DAI("ADMAIF11"), + DAI("ADMAIF12"), + DAI("ADMAIF13"), + DAI("ADMAIF14"), + DAI("ADMAIF15"), + DAI("ADMAIF16"), + DAI("ADMAIF17"), + DAI("ADMAIF18"), + DAI("ADMAIF19"), + DAI("ADMAIF20"), +}; + +static const char * const tegra_admaif_stereo_conv_text[] = { + "CH0", "CH1", "AVG", +}; + +static const char * const tegra_admaif_mono_conv_text[] = { + "Zero", "Copy", +}; + +/* + * Below macro is added to avoid looping over all ADMAIFx controls related + * to mono/stereo conversions in get()/put() callbacks. + */ +#define NV_SOC_ENUM_EXT(xname, xreg, xhandler_get, xhandler_put, xenum_text) \ +{ \ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .info = snd_soc_info_enum_double, \ + .name = xname, \ + .get = xhandler_get, \ + .put = xhandler_put, \ + .private_value = (unsigned long)&(struct soc_enum) \ + SOC_ENUM_SINGLE(xreg, 0, ARRAY_SIZE(xenum_text), xenum_text) \ +} + +#define TEGRA_ADMAIF_CIF_CTRL(reg) \ + NV_SOC_ENUM_EXT("ADMAIF" #reg " Playback Mono To Stereo", reg - 1, \ + tegra210_admaif_pget_mono_to_stereo, \ + tegra210_admaif_pput_mono_to_stereo, \ + tegra_admaif_mono_conv_text), \ + NV_SOC_ENUM_EXT("ADMAIF" #reg " Playback Stereo To Mono", reg - 1, \ + tegra210_admaif_pget_stereo_to_mono, \ + tegra210_admaif_pput_stereo_to_mono, \ + tegra_admaif_stereo_conv_text), \ + NV_SOC_ENUM_EXT("ADMAIF" #reg " Capture Mono To Stereo", reg - 1, \ + tegra210_admaif_cget_mono_to_stereo, \ + tegra210_admaif_cput_mono_to_stereo, \ + tegra_admaif_mono_conv_text), \ + NV_SOC_ENUM_EXT("ADMAIF" #reg " Capture Stereo To Mono", reg - 1, \ + tegra210_admaif_cget_stereo_to_mono, \ + tegra210_admaif_cput_stereo_to_mono, \ + tegra_admaif_stereo_conv_text) + +static struct snd_kcontrol_new tegra210_admaif_controls[] = { + TEGRA_ADMAIF_CIF_CTRL(1), + TEGRA_ADMAIF_CIF_CTRL(2), + TEGRA_ADMAIF_CIF_CTRL(3), + TEGRA_ADMAIF_CIF_CTRL(4), + TEGRA_ADMAIF_CIF_CTRL(5), + TEGRA_ADMAIF_CIF_CTRL(6), + TEGRA_ADMAIF_CIF_CTRL(7), + TEGRA_ADMAIF_CIF_CTRL(8), + TEGRA_ADMAIF_CIF_CTRL(9), + TEGRA_ADMAIF_CIF_CTRL(10), +}; + +static struct snd_kcontrol_new tegra186_admaif_controls[] = { + TEGRA_ADMAIF_CIF_CTRL(1), + TEGRA_ADMAIF_CIF_CTRL(2), + TEGRA_ADMAIF_CIF_CTRL(3), + TEGRA_ADMAIF_CIF_CTRL(4), + TEGRA_ADMAIF_CIF_CTRL(5), + TEGRA_ADMAIF_CIF_CTRL(6), + TEGRA_ADMAIF_CIF_CTRL(7), + TEGRA_ADMAIF_CIF_CTRL(8), + TEGRA_ADMAIF_CIF_CTRL(9), + TEGRA_ADMAIF_CIF_CTRL(10), + TEGRA_ADMAIF_CIF_CTRL(11), + TEGRA_ADMAIF_CIF_CTRL(12), + TEGRA_ADMAIF_CIF_CTRL(13), + TEGRA_ADMAIF_CIF_CTRL(14), + TEGRA_ADMAIF_CIF_CTRL(15), + TEGRA_ADMAIF_CIF_CTRL(16), + TEGRA_ADMAIF_CIF_CTRL(17), + TEGRA_ADMAIF_CIF_CTRL(18), + TEGRA_ADMAIF_CIF_CTRL(19), + TEGRA_ADMAIF_CIF_CTRL(20), +}; + +static const struct snd_soc_component_driver tegra210_admaif_cmpnt = { + .controls = tegra210_admaif_controls, + .num_controls = ARRAY_SIZE(tegra210_admaif_controls), + .pcm_construct = tegra_pcm_construct, + .pcm_destruct = tegra_pcm_destruct, + .open = tegra_pcm_open, + .close = tegra_pcm_close, + .hw_params = tegra_pcm_hw_params, + .hw_free = tegra_pcm_hw_free, + .mmap = tegra_pcm_mmap, + .pointer = tegra_pcm_pointer, +}; + +static const struct snd_soc_component_driver tegra186_admaif_cmpnt = { + .controls = tegra186_admaif_controls, + .num_controls = ARRAY_SIZE(tegra186_admaif_controls), + .pcm_construct = tegra_pcm_construct, + .pcm_destruct = tegra_pcm_destruct, + .open = tegra_pcm_open, + .close = tegra_pcm_close, + .hw_params = tegra_pcm_hw_params, + .hw_free = tegra_pcm_hw_free, + .mmap = tegra_pcm_mmap, + .pointer = tegra_pcm_pointer, +}; + +static const struct tegra_admaif_soc_data soc_data_tegra210 = { + .num_ch = TEGRA210_ADMAIF_CHANNEL_COUNT, + .cmpnt = &tegra210_admaif_cmpnt, + .dais = tegra210_admaif_cmpnt_dais, + .regmap_conf = &tegra210_admaif_regmap_config, + .global_base = TEGRA210_ADMAIF_GLOBAL_BASE, + .tx_base = TEGRA210_ADMAIF_TX_BASE, + .rx_base = TEGRA210_ADMAIF_RX_BASE, +}; + +static const struct tegra_admaif_soc_data soc_data_tegra186 = { + .num_ch = TEGRA186_ADMAIF_CHANNEL_COUNT, + .cmpnt = &tegra186_admaif_cmpnt, + .dais = tegra186_admaif_cmpnt_dais, + .regmap_conf = &tegra186_admaif_regmap_config, + .global_base = TEGRA186_ADMAIF_GLOBAL_BASE, + .tx_base = TEGRA186_ADMAIF_TX_BASE, + .rx_base = TEGRA186_ADMAIF_RX_BASE, +}; + +static const struct of_device_id tegra_admaif_of_match[] = { + { .compatible = "nvidia,tegra210-admaif", .data = &soc_data_tegra210 }, + { .compatible = "nvidia,tegra186-admaif", .data = &soc_data_tegra186 }, + {}, +}; +MODULE_DEVICE_TABLE(of, tegra_admaif_of_match); + +static int tegra_admaif_probe(struct platform_device *pdev) +{ + struct tegra_admaif *admaif; + void __iomem *regs; + struct resource *res; + int err, i; + + admaif = devm_kzalloc(&pdev->dev, sizeof(*admaif), GFP_KERNEL); + if (!admaif) + return -ENOMEM; + + admaif->soc_data = of_device_get_match_data(&pdev->dev); + + dev_set_drvdata(&pdev->dev, admaif); + + admaif->capture_dma_data = + devm_kcalloc(&pdev->dev, + admaif->soc_data->num_ch, + sizeof(struct snd_dmaengine_dai_dma_data), + GFP_KERNEL); + if (!admaif->capture_dma_data) + return -ENOMEM; + + admaif->playback_dma_data = + devm_kcalloc(&pdev->dev, + admaif->soc_data->num_ch, + sizeof(struct snd_dmaengine_dai_dma_data), + GFP_KERNEL); + if (!admaif->playback_dma_data) + return -ENOMEM; + + for (i = 0; i < ADMAIF_PATHS; i++) { + admaif->mono_to_stereo[i] = + devm_kcalloc(&pdev->dev, admaif->soc_data->num_ch, + sizeof(unsigned int), GFP_KERNEL); + if (!admaif->mono_to_stereo[i]) + return -ENOMEM; + + admaif->stereo_to_mono[i] = + devm_kcalloc(&pdev->dev, admaif->soc_data->num_ch, + sizeof(unsigned int), GFP_KERNEL); + if (!admaif->stereo_to_mono[i]) + return -ENOMEM; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + admaif->regmap = devm_regmap_init_mmio(&pdev->dev, regs, + admaif->soc_data->regmap_conf); + if (IS_ERR(admaif->regmap)) { + dev_err(&pdev->dev, "regmap init failed\n"); + return PTR_ERR(admaif->regmap); + } + + regcache_cache_only(admaif->regmap, true); + + regmap_update_bits(admaif->regmap, admaif->soc_data->global_base + + TEGRA_ADMAIF_GLOBAL_ENABLE, 1, 1); + + for (i = 0; i < admaif->soc_data->num_ch; i++) { + admaif->playback_dma_data[i].addr = res->start + + CH_TX_REG(TEGRA_ADMAIF_TX_FIFO_WRITE, i); + + admaif->capture_dma_data[i].addr = res->start + + CH_RX_REG(TEGRA_ADMAIF_RX_FIFO_READ, i); + + admaif->playback_dma_data[i].addr_width = 32; + + if (of_property_read_string_index(pdev->dev.of_node, + "dma-names", (i * 2) + 1, + &admaif->playback_dma_data[i].chan_name) < 0) { + dev_err(&pdev->dev, + "missing property nvidia,dma-names\n"); + + return -ENODEV; + } + + admaif->capture_dma_data[i].addr_width = 32; + + if (of_property_read_string_index(pdev->dev.of_node, + "dma-names", + (i * 2), + &admaif->capture_dma_data[i].chan_name) < 0) { + dev_err(&pdev->dev, + "missing property nvidia,dma-names\n"); + + return -ENODEV; + } + } + + err = devm_snd_soc_register_component(&pdev->dev, + admaif->soc_data->cmpnt, + admaif->soc_data->dais, + admaif->soc_data->num_ch); + if (err) { + dev_err(&pdev->dev, + "can't register ADMAIF component, err: %d\n", err); + return err; + } + + pm_runtime_enable(&pdev->dev); + + return 0; +} + +static int tegra_admaif_remove(struct platform_device *pdev) +{ + pm_runtime_disable(&pdev->dev); + + return 0; +} + +static const struct dev_pm_ops tegra_admaif_pm_ops = { + SET_RUNTIME_PM_OPS(tegra_admaif_runtime_suspend, + tegra_admaif_runtime_resume, NULL) + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) +}; + +static struct platform_driver tegra_admaif_driver = { + .probe = tegra_admaif_probe, + .remove = tegra_admaif_remove, + .driver = { + .name = "tegra210-admaif", + .of_match_table = tegra_admaif_of_match, + .pm = &tegra_admaif_pm_ops, + }, +}; +module_platform_driver(tegra_admaif_driver); + +MODULE_AUTHOR("Songhee Baek <sbaek@nvidia.com>"); +MODULE_DESCRIPTION("Tegra210 ASoC ADMAIF driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/tegra/tegra210_admaif.h b/sound/soc/tegra/tegra210_admaif.h new file mode 100644 index 000000000..96686dc92 --- /dev/null +++ b/sound/soc/tegra/tegra210_admaif.h @@ -0,0 +1,162 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * tegra210_admaif.h - Tegra ADMAIF registers + * + * Copyright (c) 2020 NVIDIA CORPORATION. All rights reserved. + * + */ + +#ifndef __TEGRA_ADMAIF_H__ +#define __TEGRA_ADMAIF_H__ + +#define TEGRA_ADMAIF_CHANNEL_REG_STRIDE 0x40 +/* Tegra210 specific */ +#define TEGRA210_ADMAIF_LAST_REG 0x75f +#define TEGRA210_ADMAIF_CHANNEL_COUNT 10 +#define TEGRA210_ADMAIF_RX_BASE 0x0 +#define TEGRA210_ADMAIF_TX_BASE 0x300 +#define TEGRA210_ADMAIF_GLOBAL_BASE 0x700 +/* Tegra186 specific */ +#define TEGRA186_ADMAIF_LAST_REG 0xd5f +#define TEGRA186_ADMAIF_CHANNEL_COUNT 20 +#define TEGRA186_ADMAIF_RX_BASE 0x0 +#define TEGRA186_ADMAIF_TX_BASE 0x500 +#define TEGRA186_ADMAIF_GLOBAL_BASE 0xd00 +/* Global registers */ +#define TEGRA_ADMAIF_GLOBAL_ENABLE 0x0 +#define TEGRA_ADMAIF_GLOBAL_CG_0 0x8 +#define TEGRA_ADMAIF_GLOBAL_STATUS 0x10 +#define TEGRA_ADMAIF_GLOBAL_RX_ENABLE_STATUS 0x20 +#define TEGRA_ADMAIF_GLOBAL_TX_ENABLE_STATUS 0x24 +/* RX channel registers */ +#define TEGRA_ADMAIF_RX_ENABLE 0x0 +#define TEGRA_ADMAIF_RX_SOFT_RESET 0x4 +#define TEGRA_ADMAIF_RX_STATUS 0xc +#define TEGRA_ADMAIF_RX_INT_STATUS 0x10 +#define TEGRA_ADMAIF_RX_INT_MASK 0x14 +#define TEGRA_ADMAIF_RX_INT_SET 0x18 +#define TEGRA_ADMAIF_RX_INT_CLEAR 0x1c +#define TEGRA_ADMAIF_CH_ACIF_RX_CTRL 0x20 +#define TEGRA_ADMAIF_RX_FIFO_CTRL 0x28 +#define TEGRA_ADMAIF_RX_FIFO_READ 0x2c +/* TX channel registers */ +#define TEGRA_ADMAIF_TX_ENABLE 0x0 +#define TEGRA_ADMAIF_TX_SOFT_RESET 0x4 +#define TEGRA_ADMAIF_TX_STATUS 0xc +#define TEGRA_ADMAIF_TX_INT_STATUS 0x10 +#define TEGRA_ADMAIF_TX_INT_MASK 0x14 +#define TEGRA_ADMAIF_TX_INT_SET 0x18 +#define TEGRA_ADMAIF_TX_INT_CLEAR 0x1c +#define TEGRA_ADMAIF_CH_ACIF_TX_CTRL 0x20 +#define TEGRA_ADMAIF_TX_FIFO_CTRL 0x28 +#define TEGRA_ADMAIF_TX_FIFO_WRITE 0x2c +/* Bit fields */ +#define PACK8_EN_SHIFT 31 +#define PACK8_EN_MASK BIT(PACK8_EN_SHIFT) +#define PACK8_EN BIT(PACK8_EN_SHIFT) +#define PACK16_EN_SHIFT 30 +#define PACK16_EN_MASK BIT(PACK16_EN_SHIFT) +#define PACK16_EN BIT(PACK16_EN_SHIFT) +#define TX_ENABLE_SHIFT 0 +#define TX_ENABLE_MASK BIT(TX_ENABLE_SHIFT) +#define TX_ENABLE BIT(TX_ENABLE_SHIFT) +#define RX_ENABLE_SHIFT 0 +#define RX_ENABLE_MASK BIT(RX_ENABLE_SHIFT) +#define RX_ENABLE BIT(RX_ENABLE_SHIFT) +#define SW_RESET_MASK 1 +#define SW_RESET 1 +/* Default values - Tegra210 */ +#define TEGRA210_ADMAIF_RX1_FIFO_CTRL_REG_DEFAULT 0x00000300 +#define TEGRA210_ADMAIF_RX2_FIFO_CTRL_REG_DEFAULT 0x00000304 +#define TEGRA210_ADMAIF_RX3_FIFO_CTRL_REG_DEFAULT 0x00000208 +#define TEGRA210_ADMAIF_RX4_FIFO_CTRL_REG_DEFAULT 0x0000020b +#define TEGRA210_ADMAIF_RX5_FIFO_CTRL_REG_DEFAULT 0x0000020e +#define TEGRA210_ADMAIF_RX6_FIFO_CTRL_REG_DEFAULT 0x00000211 +#define TEGRA210_ADMAIF_RX7_FIFO_CTRL_REG_DEFAULT 0x00000214 +#define TEGRA210_ADMAIF_RX8_FIFO_CTRL_REG_DEFAULT 0x00000217 +#define TEGRA210_ADMAIF_RX9_FIFO_CTRL_REG_DEFAULT 0x0000021a +#define TEGRA210_ADMAIF_RX10_FIFO_CTRL_REG_DEFAULT 0x0000021d +#define TEGRA210_ADMAIF_TX1_FIFO_CTRL_REG_DEFAULT 0x02000300 +#define TEGRA210_ADMAIF_TX2_FIFO_CTRL_REG_DEFAULT 0x02000304 +#define TEGRA210_ADMAIF_TX3_FIFO_CTRL_REG_DEFAULT 0x01800208 +#define TEGRA210_ADMAIF_TX4_FIFO_CTRL_REG_DEFAULT 0x0180020b +#define TEGRA210_ADMAIF_TX5_FIFO_CTRL_REG_DEFAULT 0x0180020e +#define TEGRA210_ADMAIF_TX6_FIFO_CTRL_REG_DEFAULT 0x01800211 +#define TEGRA210_ADMAIF_TX7_FIFO_CTRL_REG_DEFAULT 0x01800214 +#define TEGRA210_ADMAIF_TX8_FIFO_CTRL_REG_DEFAULT 0x01800217 +#define TEGRA210_ADMAIF_TX9_FIFO_CTRL_REG_DEFAULT 0x0180021a +#define TEGRA210_ADMAIF_TX10_FIFO_CTRL_REG_DEFAULT 0x0180021d +/* Default values - Tegra186 */ +#define TEGRA186_ADMAIF_RX1_FIFO_CTRL_REG_DEFAULT 0x00000300 +#define TEGRA186_ADMAIF_RX2_FIFO_CTRL_REG_DEFAULT 0x00000304 +#define TEGRA186_ADMAIF_RX3_FIFO_CTRL_REG_DEFAULT 0x00000308 +#define TEGRA186_ADMAIF_RX4_FIFO_CTRL_REG_DEFAULT 0x0000030c +#define TEGRA186_ADMAIF_RX5_FIFO_CTRL_REG_DEFAULT 0x00000210 +#define TEGRA186_ADMAIF_RX6_FIFO_CTRL_REG_DEFAULT 0x00000213 +#define TEGRA186_ADMAIF_RX7_FIFO_CTRL_REG_DEFAULT 0x00000216 +#define TEGRA186_ADMAIF_RX8_FIFO_CTRL_REG_DEFAULT 0x00000219 +#define TEGRA186_ADMAIF_RX9_FIFO_CTRL_REG_DEFAULT 0x0000021c +#define TEGRA186_ADMAIF_RX10_FIFO_CTRL_REG_DEFAULT 0x0000021f +#define TEGRA186_ADMAIF_RX11_FIFO_CTRL_REG_DEFAULT 0x00000222 +#define TEGRA186_ADMAIF_RX12_FIFO_CTRL_REG_DEFAULT 0x00000225 +#define TEGRA186_ADMAIF_RX13_FIFO_CTRL_REG_DEFAULT 0x00000228 +#define TEGRA186_ADMAIF_RX14_FIFO_CTRL_REG_DEFAULT 0x0000022b +#define TEGRA186_ADMAIF_RX15_FIFO_CTRL_REG_DEFAULT 0x0000022e +#define TEGRA186_ADMAIF_RX16_FIFO_CTRL_REG_DEFAULT 0x00000231 +#define TEGRA186_ADMAIF_RX17_FIFO_CTRL_REG_DEFAULT 0x00000234 +#define TEGRA186_ADMAIF_RX18_FIFO_CTRL_REG_DEFAULT 0x00000237 +#define TEGRA186_ADMAIF_RX19_FIFO_CTRL_REG_DEFAULT 0x0000023a +#define TEGRA186_ADMAIF_RX20_FIFO_CTRL_REG_DEFAULT 0x0000023d +#define TEGRA186_ADMAIF_TX1_FIFO_CTRL_REG_DEFAULT 0x02000300 +#define TEGRA186_ADMAIF_TX2_FIFO_CTRL_REG_DEFAULT 0x02000304 +#define TEGRA186_ADMAIF_TX3_FIFO_CTRL_REG_DEFAULT 0x02000308 +#define TEGRA186_ADMAIF_TX4_FIFO_CTRL_REG_DEFAULT 0x0200030c +#define TEGRA186_ADMAIF_TX5_FIFO_CTRL_REG_DEFAULT 0x01800210 +#define TEGRA186_ADMAIF_TX6_FIFO_CTRL_REG_DEFAULT 0x01800213 +#define TEGRA186_ADMAIF_TX7_FIFO_CTRL_REG_DEFAULT 0x01800216 +#define TEGRA186_ADMAIF_TX8_FIFO_CTRL_REG_DEFAULT 0x01800219 +#define TEGRA186_ADMAIF_TX9_FIFO_CTRL_REG_DEFAULT 0x0180021c +#define TEGRA186_ADMAIF_TX10_FIFO_CTRL_REG_DEFAULT 0x0180021f +#define TEGRA186_ADMAIF_TX11_FIFO_CTRL_REG_DEFAULT 0x01800222 +#define TEGRA186_ADMAIF_TX12_FIFO_CTRL_REG_DEFAULT 0x01800225 +#define TEGRA186_ADMAIF_TX13_FIFO_CTRL_REG_DEFAULT 0x01800228 +#define TEGRA186_ADMAIF_TX14_FIFO_CTRL_REG_DEFAULT 0x0180022b +#define TEGRA186_ADMAIF_TX15_FIFO_CTRL_REG_DEFAULT 0x0180022e +#define TEGRA186_ADMAIF_TX16_FIFO_CTRL_REG_DEFAULT 0x01800231 +#define TEGRA186_ADMAIF_TX17_FIFO_CTRL_REG_DEFAULT 0x01800234 +#define TEGRA186_ADMAIF_TX18_FIFO_CTRL_REG_DEFAULT 0x01800237 +#define TEGRA186_ADMAIF_TX19_FIFO_CTRL_REG_DEFAULT 0x0180023a +#define TEGRA186_ADMAIF_TX20_FIFO_CTRL_REG_DEFAULT 0x0180023d + +enum { + DATA_8BIT, + DATA_16BIT, + DATA_32BIT +}; + +enum { + ADMAIF_RX_PATH, + ADMAIF_TX_PATH, + ADMAIF_PATHS, +}; + +struct tegra_admaif_soc_data { + const struct snd_soc_component_driver *cmpnt; + const struct regmap_config *regmap_conf; + struct snd_soc_dai_driver *dais; + unsigned int global_base; + unsigned int tx_base; + unsigned int rx_base; + unsigned int num_ch; +}; + +struct tegra_admaif { + struct snd_dmaengine_dai_dma_data *capture_dma_data; + struct snd_dmaengine_dai_dma_data *playback_dma_data; + const struct tegra_admaif_soc_data *soc_data; + unsigned int *mono_to_stereo[ADMAIF_PATHS]; + unsigned int *stereo_to_mono[ADMAIF_PATHS]; + struct regmap *regmap; +}; + +#endif diff --git a/sound/soc/tegra/tegra210_ahub.c b/sound/soc/tegra/tegra210_ahub.c new file mode 100644 index 000000000..1b2f7cb8c --- /dev/null +++ b/sound/soc/tegra/tegra210_ahub.c @@ -0,0 +1,679 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// tegra210_ahub.c - Tegra210 AHUB driver +// +// Copyright (c) 2020 NVIDIA CORPORATION. All rights reserved. + +#include <linux/clk.h> +#include <linux/device.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 <sound/soc.h> +#include "tegra210_ahub.h" + +static int tegra_ahub_get_value_enum(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *uctl) +{ + struct snd_soc_component *cmpnt = snd_soc_dapm_kcontrol_component(kctl); + struct tegra_ahub *ahub = snd_soc_component_get_drvdata(cmpnt); + struct soc_enum *e = (struct soc_enum *)kctl->private_value; + unsigned int reg, i, bit_pos = 0; + + /* + * Find the bit position of current MUX input. + * If nothing is set, position would be 0 and it corresponds to 'None'. + */ + for (i = 0; i < ahub->soc_data->reg_count; i++) { + unsigned int reg_val; + + reg = e->reg + (TEGRA210_XBAR_PART1_RX * i); + reg_val = snd_soc_component_read(cmpnt, reg); + reg_val &= ahub->soc_data->mask[i]; + + if (reg_val) { + bit_pos = ffs(reg_val) + + (8 * cmpnt->val_bytes * i); + break; + } + } + + /* Find index related to the item in array *_ahub_mux_texts[] */ + for (i = 0; i < e->items; i++) { + if (bit_pos == e->values[i]) { + uctl->value.enumerated.item[0] = i; + break; + } + } + + return 0; +} + +static int tegra_ahub_put_value_enum(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *uctl) +{ + struct snd_soc_component *cmpnt = snd_soc_dapm_kcontrol_component(kctl); + struct tegra_ahub *ahub = snd_soc_component_get_drvdata(cmpnt); + struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kctl); + struct soc_enum *e = (struct soc_enum *)kctl->private_value; + struct snd_soc_dapm_update update[TEGRA_XBAR_UPDATE_MAX_REG] = { }; + unsigned int *item = uctl->value.enumerated.item; + unsigned int value = e->values[item[0]]; + unsigned int i, bit_pos, reg_idx = 0, reg_val = 0; + int change = 0; + + if (item[0] >= e->items) + return -EINVAL; + + if (value) { + /* Get the register index and value to set */ + reg_idx = (value - 1) / (8 * cmpnt->val_bytes); + bit_pos = (value - 1) % (8 * cmpnt->val_bytes); + reg_val = BIT(bit_pos); + } + + /* + * Run through all parts of a MUX register to find the state changes. + * There will be an additional update if new MUX input value is from + * different part of the MUX register. + */ + for (i = 0; i < ahub->soc_data->reg_count; i++) { + update[i].reg = e->reg + (TEGRA210_XBAR_PART1_RX * i); + update[i].val = (i == reg_idx) ? reg_val : 0; + update[i].mask = ahub->soc_data->mask[i]; + update[i].kcontrol = kctl; + + /* Update widget power if state has changed */ + if (snd_soc_component_test_bits(cmpnt, update[i].reg, + update[i].mask, + update[i].val)) + change |= snd_soc_dapm_mux_update_power(dapm, kctl, + item[0], e, + &update[i]); + } + + return change; +} + +static struct snd_soc_dai_driver tegra210_ahub_dais[] = { + DAI(ADMAIF1), + DAI(ADMAIF2), + DAI(ADMAIF3), + DAI(ADMAIF4), + DAI(ADMAIF5), + DAI(ADMAIF6), + DAI(ADMAIF7), + DAI(ADMAIF8), + DAI(ADMAIF9), + DAI(ADMAIF10), + DAI(I2S1), + DAI(I2S2), + DAI(I2S3), + DAI(I2S4), + DAI(I2S5), + DAI(DMIC1), + DAI(DMIC2), + DAI(DMIC3), +}; + +static struct snd_soc_dai_driver tegra186_ahub_dais[] = { + DAI(ADMAIF1), + DAI(ADMAIF2), + DAI(ADMAIF3), + DAI(ADMAIF4), + DAI(ADMAIF5), + DAI(ADMAIF6), + DAI(ADMAIF7), + DAI(ADMAIF8), + DAI(ADMAIF9), + DAI(ADMAIF10), + DAI(ADMAIF11), + DAI(ADMAIF12), + DAI(ADMAIF13), + DAI(ADMAIF14), + DAI(ADMAIF15), + DAI(ADMAIF16), + DAI(ADMAIF17), + DAI(ADMAIF18), + DAI(ADMAIF19), + DAI(ADMAIF20), + DAI(I2S1), + DAI(I2S2), + DAI(I2S3), + DAI(I2S4), + DAI(I2S5), + DAI(I2S6), + DAI(DMIC1), + DAI(DMIC2), + DAI(DMIC3), + DAI(DMIC4), + DAI(DSPK1), + DAI(DSPK2), +}; + +static const char * const tegra210_ahub_mux_texts[] = { + "None", + "ADMAIF1", + "ADMAIF2", + "ADMAIF3", + "ADMAIF4", + "ADMAIF5", + "ADMAIF6", + "ADMAIF7", + "ADMAIF8", + "ADMAIF9", + "ADMAIF10", + "I2S1", + "I2S2", + "I2S3", + "I2S4", + "I2S5", + "DMIC1", + "DMIC2", + "DMIC3", +}; + +static const char * const tegra186_ahub_mux_texts[] = { + "None", + "ADMAIF1", + "ADMAIF2", + "ADMAIF3", + "ADMAIF4", + "ADMAIF5", + "ADMAIF6", + "ADMAIF7", + "ADMAIF8", + "ADMAIF9", + "ADMAIF10", + "ADMAIF11", + "ADMAIF12", + "ADMAIF13", + "ADMAIF14", + "ADMAIF15", + "ADMAIF16", + "I2S1", + "I2S2", + "I2S3", + "I2S4", + "I2S5", + "I2S6", + "ADMAIF17", + "ADMAIF18", + "ADMAIF19", + "ADMAIF20", + "DMIC1", + "DMIC2", + "DMIC3", + "DMIC4", +}; + +static const unsigned int tegra210_ahub_mux_values[] = { + 0, + MUX_VALUE(0, 0), + MUX_VALUE(0, 1), + MUX_VALUE(0, 2), + MUX_VALUE(0, 3), + MUX_VALUE(0, 4), + MUX_VALUE(0, 5), + MUX_VALUE(0, 6), + MUX_VALUE(0, 7), + MUX_VALUE(0, 8), + MUX_VALUE(0, 9), + MUX_VALUE(0, 16), + MUX_VALUE(0, 17), + MUX_VALUE(0, 18), + MUX_VALUE(0, 19), + MUX_VALUE(0, 20), + MUX_VALUE(2, 18), + MUX_VALUE(2, 19), + MUX_VALUE(2, 20), +}; + +static const unsigned int tegra186_ahub_mux_values[] = { + 0, + MUX_VALUE(0, 0), + MUX_VALUE(0, 1), + MUX_VALUE(0, 2), + MUX_VALUE(0, 3), + MUX_VALUE(0, 4), + MUX_VALUE(0, 5), + MUX_VALUE(0, 6), + MUX_VALUE(0, 7), + MUX_VALUE(0, 8), + MUX_VALUE(0, 9), + MUX_VALUE(0, 10), + MUX_VALUE(0, 11), + MUX_VALUE(0, 12), + MUX_VALUE(0, 13), + MUX_VALUE(0, 14), + MUX_VALUE(0, 15), + MUX_VALUE(0, 16), + MUX_VALUE(0, 17), + MUX_VALUE(0, 18), + MUX_VALUE(0, 19), + MUX_VALUE(0, 20), + MUX_VALUE(0, 21), + MUX_VALUE(3, 16), + MUX_VALUE(3, 17), + MUX_VALUE(3, 18), + MUX_VALUE(3, 19), + MUX_VALUE(2, 18), + MUX_VALUE(2, 19), + MUX_VALUE(2, 20), + MUX_VALUE(2, 21), +}; + +/* Controls for t210 */ +MUX_ENUM_CTRL_DECL(t210_admaif1_tx, 0x00); +MUX_ENUM_CTRL_DECL(t210_admaif2_tx, 0x01); +MUX_ENUM_CTRL_DECL(t210_admaif3_tx, 0x02); +MUX_ENUM_CTRL_DECL(t210_admaif4_tx, 0x03); +MUX_ENUM_CTRL_DECL(t210_admaif5_tx, 0x04); +MUX_ENUM_CTRL_DECL(t210_admaif6_tx, 0x05); +MUX_ENUM_CTRL_DECL(t210_admaif7_tx, 0x06); +MUX_ENUM_CTRL_DECL(t210_admaif8_tx, 0x07); +MUX_ENUM_CTRL_DECL(t210_admaif9_tx, 0x08); +MUX_ENUM_CTRL_DECL(t210_admaif10_tx, 0x09); +MUX_ENUM_CTRL_DECL(t210_i2s1_tx, 0x10); +MUX_ENUM_CTRL_DECL(t210_i2s2_tx, 0x11); +MUX_ENUM_CTRL_DECL(t210_i2s3_tx, 0x12); +MUX_ENUM_CTRL_DECL(t210_i2s4_tx, 0x13); +MUX_ENUM_CTRL_DECL(t210_i2s5_tx, 0x14); + +/* Controls for t186 */ +MUX_ENUM_CTRL_DECL_186(t186_admaif1_tx, 0x00); +MUX_ENUM_CTRL_DECL_186(t186_admaif2_tx, 0x01); +MUX_ENUM_CTRL_DECL_186(t186_admaif3_tx, 0x02); +MUX_ENUM_CTRL_DECL_186(t186_admaif4_tx, 0x03); +MUX_ENUM_CTRL_DECL_186(t186_admaif5_tx, 0x04); +MUX_ENUM_CTRL_DECL_186(t186_admaif6_tx, 0x05); +MUX_ENUM_CTRL_DECL_186(t186_admaif7_tx, 0x06); +MUX_ENUM_CTRL_DECL_186(t186_admaif8_tx, 0x07); +MUX_ENUM_CTRL_DECL_186(t186_admaif9_tx, 0x08); +MUX_ENUM_CTRL_DECL_186(t186_admaif10_tx, 0x09); +MUX_ENUM_CTRL_DECL_186(t186_i2s1_tx, 0x10); +MUX_ENUM_CTRL_DECL_186(t186_i2s2_tx, 0x11); +MUX_ENUM_CTRL_DECL_186(t186_i2s3_tx, 0x12); +MUX_ENUM_CTRL_DECL_186(t186_i2s4_tx, 0x13); +MUX_ENUM_CTRL_DECL_186(t186_i2s5_tx, 0x14); +MUX_ENUM_CTRL_DECL_186(t186_admaif11_tx, 0x0a); +MUX_ENUM_CTRL_DECL_186(t186_admaif12_tx, 0x0b); +MUX_ENUM_CTRL_DECL_186(t186_admaif13_tx, 0x0c); +MUX_ENUM_CTRL_DECL_186(t186_admaif14_tx, 0x0d); +MUX_ENUM_CTRL_DECL_186(t186_admaif15_tx, 0x0e); +MUX_ENUM_CTRL_DECL_186(t186_admaif16_tx, 0x0f); +MUX_ENUM_CTRL_DECL_186(t186_i2s6_tx, 0x15); +MUX_ENUM_CTRL_DECL_186(t186_dspk1_tx, 0x30); +MUX_ENUM_CTRL_DECL_186(t186_dspk2_tx, 0x31); +MUX_ENUM_CTRL_DECL_186(t186_admaif17_tx, 0x68); +MUX_ENUM_CTRL_DECL_186(t186_admaif18_tx, 0x69); +MUX_ENUM_CTRL_DECL_186(t186_admaif19_tx, 0x6a); +MUX_ENUM_CTRL_DECL_186(t186_admaif20_tx, 0x6b); + +/* + * The number of entries in, and order of, this array is closely tied to the + * calculation of tegra210_ahub_codec.num_dapm_widgets near the end of + * tegra210_ahub_probe() + */ +static const struct snd_soc_dapm_widget tegra210_ahub_widgets[] = { + WIDGETS("ADMAIF1", t210_admaif1_tx), + WIDGETS("ADMAIF2", t210_admaif2_tx), + WIDGETS("ADMAIF3", t210_admaif3_tx), + WIDGETS("ADMAIF4", t210_admaif4_tx), + WIDGETS("ADMAIF5", t210_admaif5_tx), + WIDGETS("ADMAIF6", t210_admaif6_tx), + WIDGETS("ADMAIF7", t210_admaif7_tx), + WIDGETS("ADMAIF8", t210_admaif8_tx), + WIDGETS("ADMAIF9", t210_admaif9_tx), + WIDGETS("ADMAIF10", t210_admaif10_tx), + WIDGETS("I2S1", t210_i2s1_tx), + WIDGETS("I2S2", t210_i2s2_tx), + WIDGETS("I2S3", t210_i2s3_tx), + WIDGETS("I2S4", t210_i2s4_tx), + WIDGETS("I2S5", t210_i2s5_tx), + TX_WIDGETS("DMIC1"), + TX_WIDGETS("DMIC2"), + TX_WIDGETS("DMIC3"), +}; + +static const struct snd_soc_dapm_widget tegra186_ahub_widgets[] = { + WIDGETS("ADMAIF1", t186_admaif1_tx), + WIDGETS("ADMAIF2", t186_admaif2_tx), + WIDGETS("ADMAIF3", t186_admaif3_tx), + WIDGETS("ADMAIF4", t186_admaif4_tx), + WIDGETS("ADMAIF5", t186_admaif5_tx), + WIDGETS("ADMAIF6", t186_admaif6_tx), + WIDGETS("ADMAIF7", t186_admaif7_tx), + WIDGETS("ADMAIF8", t186_admaif8_tx), + WIDGETS("ADMAIF9", t186_admaif9_tx), + WIDGETS("ADMAIF10", t186_admaif10_tx), + WIDGETS("ADMAIF11", t186_admaif11_tx), + WIDGETS("ADMAIF12", t186_admaif12_tx), + WIDGETS("ADMAIF13", t186_admaif13_tx), + WIDGETS("ADMAIF14", t186_admaif14_tx), + WIDGETS("ADMAIF15", t186_admaif15_tx), + WIDGETS("ADMAIF16", t186_admaif16_tx), + WIDGETS("ADMAIF17", t186_admaif17_tx), + WIDGETS("ADMAIF18", t186_admaif18_tx), + WIDGETS("ADMAIF19", t186_admaif19_tx), + WIDGETS("ADMAIF20", t186_admaif20_tx), + WIDGETS("I2S1", t186_i2s1_tx), + WIDGETS("I2S2", t186_i2s2_tx), + WIDGETS("I2S3", t186_i2s3_tx), + WIDGETS("I2S4", t186_i2s4_tx), + WIDGETS("I2S5", t186_i2s5_tx), + WIDGETS("I2S6", t186_i2s6_tx), + TX_WIDGETS("DMIC1"), + TX_WIDGETS("DMIC2"), + TX_WIDGETS("DMIC3"), + TX_WIDGETS("DMIC4"), + WIDGETS("DSPK1", t186_dspk1_tx), + WIDGETS("DSPK2", t186_dspk2_tx), +}; + +#define TEGRA_COMMON_MUX_ROUTES(name) \ + { name " XBAR-TX", NULL, name " Mux" }, \ + { name " Mux", "ADMAIF1", "ADMAIF1 XBAR-RX" }, \ + { name " Mux", "ADMAIF2", "ADMAIF2 XBAR-RX" }, \ + { name " Mux", "ADMAIF3", "ADMAIF3 XBAR-RX" }, \ + { name " Mux", "ADMAIF4", "ADMAIF4 XBAR-RX" }, \ + { name " Mux", "ADMAIF5", "ADMAIF5 XBAR-RX" }, \ + { name " Mux", "ADMAIF6", "ADMAIF6 XBAR-RX" }, \ + { name " Mux", "ADMAIF7", "ADMAIF7 XBAR-RX" }, \ + { name " Mux", "ADMAIF8", "ADMAIF8 XBAR-RX" }, \ + { name " Mux", "ADMAIF9", "ADMAIF9 XBAR-RX" }, \ + { name " Mux", "ADMAIF10", "ADMAIF10 XBAR-RX" }, \ + { name " Mux", "I2S1", "I2S1 XBAR-RX" }, \ + { name " Mux", "I2S2", "I2S2 XBAR-RX" }, \ + { name " Mux", "I2S3", "I2S3 XBAR-RX" }, \ + { name " Mux", "I2S4", "I2S4 XBAR-RX" }, \ + { name " Mux", "I2S5", "I2S5 XBAR-RX" }, \ + { name " Mux", "DMIC1", "DMIC1 XBAR-RX" }, \ + { name " Mux", "DMIC2", "DMIC2 XBAR-RX" }, \ + { name " Mux", "DMIC3", "DMIC3 XBAR-RX" }, + +#define TEGRA186_ONLY_MUX_ROUTES(name) \ + { name " Mux", "ADMAIF11", "ADMAIF11 XBAR-RX" }, \ + { name " Mux", "ADMAIF12", "ADMAIF12 XBAR-RX" }, \ + { name " Mux", "ADMAIF13", "ADMAIF13 XBAR-RX" }, \ + { name " Mux", "ADMAIF14", "ADMAIF14 XBAR-RX" }, \ + { name " Mux", "ADMAIF15", "ADMAIF15 XBAR-RX" }, \ + { name " Mux", "ADMAIF16", "ADMAIF16 XBAR-RX" }, \ + { name " Mux", "ADMAIF17", "ADMAIF17 XBAR-RX" }, \ + { name " Mux", "ADMAIF18", "ADMAIF18 XBAR-RX" }, \ + { name " Mux", "ADMAIF19", "ADMAIF19 XBAR-RX" }, \ + { name " Mux", "ADMAIF20", "ADMAIF20 XBAR-RX" }, \ + { name " Mux", "I2S6", "I2S6 XBAR-RX" }, \ + { name " Mux", "DMIC4", "DMIC4 XBAR-RX" }, + +#define TEGRA210_MUX_ROUTES(name) \ + TEGRA_COMMON_MUX_ROUTES(name) + +#define TEGRA186_MUX_ROUTES(name) \ + TEGRA_COMMON_MUX_ROUTES(name) \ + TEGRA186_ONLY_MUX_ROUTES(name) + +/* Connect FEs with XBAR */ +#define TEGRA_FE_ROUTES(name) \ + { name " XBAR-Playback", NULL, name " Playback" }, \ + { name " XBAR-RX", NULL, name " XBAR-Playback"}, \ + { name " XBAR-Capture", NULL, name " XBAR-TX" }, \ + { name " Capture", NULL, name " XBAR-Capture" }, + +/* + * The number of entries in, and order of, this array is closely tied to the + * calculation of tegra210_ahub_codec.num_dapm_routes near the end of + * tegra210_ahub_probe() + */ +static const struct snd_soc_dapm_route tegra210_ahub_routes[] = { + TEGRA_FE_ROUTES("ADMAIF1") + TEGRA_FE_ROUTES("ADMAIF2") + TEGRA_FE_ROUTES("ADMAIF3") + TEGRA_FE_ROUTES("ADMAIF4") + TEGRA_FE_ROUTES("ADMAIF5") + TEGRA_FE_ROUTES("ADMAIF6") + TEGRA_FE_ROUTES("ADMAIF7") + TEGRA_FE_ROUTES("ADMAIF8") + TEGRA_FE_ROUTES("ADMAIF9") + TEGRA_FE_ROUTES("ADMAIF10") + TEGRA210_MUX_ROUTES("ADMAIF1") + TEGRA210_MUX_ROUTES("ADMAIF2") + TEGRA210_MUX_ROUTES("ADMAIF3") + TEGRA210_MUX_ROUTES("ADMAIF4") + TEGRA210_MUX_ROUTES("ADMAIF5") + TEGRA210_MUX_ROUTES("ADMAIF6") + TEGRA210_MUX_ROUTES("ADMAIF7") + TEGRA210_MUX_ROUTES("ADMAIF8") + TEGRA210_MUX_ROUTES("ADMAIF9") + TEGRA210_MUX_ROUTES("ADMAIF10") + TEGRA210_MUX_ROUTES("I2S1") + TEGRA210_MUX_ROUTES("I2S2") + TEGRA210_MUX_ROUTES("I2S3") + TEGRA210_MUX_ROUTES("I2S4") + TEGRA210_MUX_ROUTES("I2S5") +}; + +static const struct snd_soc_dapm_route tegra186_ahub_routes[] = { + TEGRA_FE_ROUTES("ADMAIF1") + TEGRA_FE_ROUTES("ADMAIF2") + TEGRA_FE_ROUTES("ADMAIF3") + TEGRA_FE_ROUTES("ADMAIF4") + TEGRA_FE_ROUTES("ADMAIF5") + TEGRA_FE_ROUTES("ADMAIF6") + TEGRA_FE_ROUTES("ADMAIF7") + TEGRA_FE_ROUTES("ADMAIF8") + TEGRA_FE_ROUTES("ADMAIF9") + TEGRA_FE_ROUTES("ADMAIF10") + TEGRA_FE_ROUTES("ADMAIF11") + TEGRA_FE_ROUTES("ADMAIF12") + TEGRA_FE_ROUTES("ADMAIF13") + TEGRA_FE_ROUTES("ADMAIF14") + TEGRA_FE_ROUTES("ADMAIF15") + TEGRA_FE_ROUTES("ADMAIF16") + TEGRA_FE_ROUTES("ADMAIF17") + TEGRA_FE_ROUTES("ADMAIF18") + TEGRA_FE_ROUTES("ADMAIF19") + TEGRA_FE_ROUTES("ADMAIF20") + TEGRA186_MUX_ROUTES("ADMAIF1") + TEGRA186_MUX_ROUTES("ADMAIF2") + TEGRA186_MUX_ROUTES("ADMAIF3") + TEGRA186_MUX_ROUTES("ADMAIF4") + TEGRA186_MUX_ROUTES("ADMAIF5") + TEGRA186_MUX_ROUTES("ADMAIF6") + TEGRA186_MUX_ROUTES("ADMAIF7") + TEGRA186_MUX_ROUTES("ADMAIF8") + TEGRA186_MUX_ROUTES("ADMAIF9") + TEGRA186_MUX_ROUTES("ADMAIF10") + TEGRA186_MUX_ROUTES("ADMAIF11") + TEGRA186_MUX_ROUTES("ADMAIF12") + TEGRA186_MUX_ROUTES("ADMAIF13") + TEGRA186_MUX_ROUTES("ADMAIF14") + TEGRA186_MUX_ROUTES("ADMAIF15") + TEGRA186_MUX_ROUTES("ADMAIF16") + TEGRA186_MUX_ROUTES("ADMAIF17") + TEGRA186_MUX_ROUTES("ADMAIF18") + TEGRA186_MUX_ROUTES("ADMAIF19") + TEGRA186_MUX_ROUTES("ADMAIF20") + TEGRA186_MUX_ROUTES("I2S1") + TEGRA186_MUX_ROUTES("I2S2") + TEGRA186_MUX_ROUTES("I2S3") + TEGRA186_MUX_ROUTES("I2S4") + TEGRA186_MUX_ROUTES("I2S5") + TEGRA186_MUX_ROUTES("I2S6") + TEGRA186_MUX_ROUTES("DSPK1") + TEGRA186_MUX_ROUTES("DSPK2") +}; + +static const struct snd_soc_component_driver tegra210_ahub_component = { + .dapm_widgets = tegra210_ahub_widgets, + .num_dapm_widgets = ARRAY_SIZE(tegra210_ahub_widgets), + .dapm_routes = tegra210_ahub_routes, + .num_dapm_routes = ARRAY_SIZE(tegra210_ahub_routes), +}; + +static const struct snd_soc_component_driver tegra186_ahub_component = { + .dapm_widgets = tegra186_ahub_widgets, + .num_dapm_widgets = ARRAY_SIZE(tegra186_ahub_widgets), + .dapm_routes = tegra186_ahub_routes, + .num_dapm_routes = ARRAY_SIZE(tegra186_ahub_routes), +}; + +static const struct regmap_config tegra210_ahub_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = TEGRA210_MAX_REGISTER_ADDR, + .cache_type = REGCACHE_FLAT, +}; + +static const struct regmap_config tegra186_ahub_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = TEGRA186_MAX_REGISTER_ADDR, + .cache_type = REGCACHE_FLAT, +}; + +static const struct tegra_ahub_soc_data soc_data_tegra210 = { + .cmpnt_drv = &tegra210_ahub_component, + .dai_drv = tegra210_ahub_dais, + .num_dais = ARRAY_SIZE(tegra210_ahub_dais), + .regmap_config = &tegra210_ahub_regmap_config, + .mask[0] = TEGRA210_XBAR_REG_MASK_0, + .mask[1] = TEGRA210_XBAR_REG_MASK_1, + .mask[2] = TEGRA210_XBAR_REG_MASK_2, + .mask[3] = TEGRA210_XBAR_REG_MASK_3, + .reg_count = TEGRA210_XBAR_UPDATE_MAX_REG, +}; + +static const struct tegra_ahub_soc_data soc_data_tegra186 = { + .cmpnt_drv = &tegra186_ahub_component, + .dai_drv = tegra186_ahub_dais, + .num_dais = ARRAY_SIZE(tegra186_ahub_dais), + .regmap_config = &tegra186_ahub_regmap_config, + .mask[0] = TEGRA186_XBAR_REG_MASK_0, + .mask[1] = TEGRA186_XBAR_REG_MASK_1, + .mask[2] = TEGRA186_XBAR_REG_MASK_2, + .mask[3] = TEGRA186_XBAR_REG_MASK_3, + .reg_count = TEGRA186_XBAR_UPDATE_MAX_REG, +}; + +static const struct of_device_id tegra_ahub_of_match[] = { + { .compatible = "nvidia,tegra210-ahub", .data = &soc_data_tegra210 }, + { .compatible = "nvidia,tegra186-ahub", .data = &soc_data_tegra186 }, + {}, +}; +MODULE_DEVICE_TABLE(of, tegra_ahub_of_match); + +static int __maybe_unused tegra_ahub_runtime_suspend(struct device *dev) +{ + struct tegra_ahub *ahub = dev_get_drvdata(dev); + + regcache_cache_only(ahub->regmap, true); + regcache_mark_dirty(ahub->regmap); + + clk_disable_unprepare(ahub->clk); + + return 0; +} + +static int __maybe_unused tegra_ahub_runtime_resume(struct device *dev) +{ + struct tegra_ahub *ahub = dev_get_drvdata(dev); + int err; + + err = clk_prepare_enable(ahub->clk); + if (err) { + dev_err(dev, "failed to enable AHUB clock, err: %d\n", err); + return err; + } + + regcache_cache_only(ahub->regmap, false); + regcache_sync(ahub->regmap); + + return 0; +} + +static int tegra_ahub_probe(struct platform_device *pdev) +{ + struct tegra_ahub *ahub; + void __iomem *regs; + int err; + + ahub = devm_kzalloc(&pdev->dev, sizeof(*ahub), GFP_KERNEL); + if (!ahub) + return -ENOMEM; + + ahub->soc_data = of_device_get_match_data(&pdev->dev); + + platform_set_drvdata(pdev, ahub); + + ahub->clk = devm_clk_get(&pdev->dev, "ahub"); + if (IS_ERR(ahub->clk)) { + dev_err(&pdev->dev, "can't retrieve AHUB clock\n"); + return PTR_ERR(ahub->clk); + } + + regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + ahub->regmap = devm_regmap_init_mmio(&pdev->dev, regs, + ahub->soc_data->regmap_config); + if (IS_ERR(ahub->regmap)) { + dev_err(&pdev->dev, "regmap init failed\n"); + return PTR_ERR(ahub->regmap); + } + + regcache_cache_only(ahub->regmap, true); + + err = devm_snd_soc_register_component(&pdev->dev, + ahub->soc_data->cmpnt_drv, + ahub->soc_data->dai_drv, + ahub->soc_data->num_dais); + if (err) { + dev_err(&pdev->dev, "can't register AHUB component, err: %d\n", + err); + return err; + } + + err = of_platform_populate(pdev->dev.of_node, NULL, NULL, &pdev->dev); + if (err) + return err; + + pm_runtime_enable(&pdev->dev); + + return 0; +} + +static int tegra_ahub_remove(struct platform_device *pdev) +{ + pm_runtime_disable(&pdev->dev); + + return 0; +} + +static const struct dev_pm_ops tegra_ahub_pm_ops = { + SET_RUNTIME_PM_OPS(tegra_ahub_runtime_suspend, + tegra_ahub_runtime_resume, NULL) + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) +}; + +static struct platform_driver tegra_ahub_driver = { + .probe = tegra_ahub_probe, + .remove = tegra_ahub_remove, + .driver = { + .name = "tegra210-ahub", + .of_match_table = tegra_ahub_of_match, + .pm = &tegra_ahub_pm_ops, + }, +}; +module_platform_driver(tegra_ahub_driver); + +MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>"); +MODULE_AUTHOR("Mohan Kumar <mkumard@nvidia.com>"); +MODULE_DESCRIPTION("Tegra210 ASoC AHUB driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/tegra/tegra210_ahub.h b/sound/soc/tegra/tegra210_ahub.h new file mode 100644 index 000000000..47802bbe1 --- /dev/null +++ b/sound/soc/tegra/tegra210_ahub.h @@ -0,0 +1,127 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * tegra210_ahub.h - TEGRA210 AHUB + * + * Copyright (c) 2020 NVIDIA CORPORATION. All rights reserved. + * + */ + +#ifndef __TEGRA210_AHUB__H__ +#define __TEGRA210_AHUB__H__ + +/* Tegra210 specific */ +#define TEGRA210_XBAR_PART1_RX 0x200 +#define TEGRA210_XBAR_PART2_RX 0x400 +#define TEGRA210_XBAR_RX_STRIDE 0x4 +#define TEGRA210_XBAR_AUDIO_RX_COUNT 90 +#define TEGRA210_XBAR_REG_MASK_0 0xf1f03ff +#define TEGRA210_XBAR_REG_MASK_1 0x3f30031f +#define TEGRA210_XBAR_REG_MASK_2 0xff1cf313 +#define TEGRA210_XBAR_REG_MASK_3 0x0 +#define TEGRA210_XBAR_UPDATE_MAX_REG 3 +/* Tegra186 specific */ +#define TEGRA186_XBAR_PART3_RX 0x600 +#define TEGRA186_XBAR_AUDIO_RX_COUNT 115 +#define TEGRA186_XBAR_REG_MASK_0 0xf3fffff +#define TEGRA186_XBAR_REG_MASK_1 0x3f310f1f +#define TEGRA186_XBAR_REG_MASK_2 0xff3cf311 +#define TEGRA186_XBAR_REG_MASK_3 0x3f0f00ff +#define TEGRA186_XBAR_UPDATE_MAX_REG 4 + +#define TEGRA_XBAR_UPDATE_MAX_REG (TEGRA186_XBAR_UPDATE_MAX_REG) + +#define TEGRA186_MAX_REGISTER_ADDR (TEGRA186_XBAR_PART3_RX + \ + (TEGRA210_XBAR_RX_STRIDE * (TEGRA186_XBAR_AUDIO_RX_COUNT - 1))) + +#define TEGRA210_MAX_REGISTER_ADDR (TEGRA210_XBAR_PART2_RX + \ + (TEGRA210_XBAR_RX_STRIDE * (TEGRA210_XBAR_AUDIO_RX_COUNT - 1))) + +#define MUX_REG(id) (TEGRA210_XBAR_RX_STRIDE * (id)) + +#define MUX_VALUE(npart, nbit) (1 + (nbit) + (npart) * 32) + +#define SOC_VALUE_ENUM_WIDE(xreg, shift, xmax, xtexts, xvalues) \ + { \ + .reg = xreg, \ + .shift_l = shift, \ + .shift_r = shift, \ + .items = xmax, \ + .texts = xtexts, \ + .values = xvalues, \ + .mask = xmax ? roundup_pow_of_two(xmax) - 1 : 0 \ + } + +#define SOC_VALUE_ENUM_WIDE_DECL(name, xreg, shift, xtexts, xvalues) \ + static struct soc_enum name = \ + SOC_VALUE_ENUM_WIDE(xreg, shift, ARRAY_SIZE(xtexts), \ + xtexts, xvalues) + +#define MUX_ENUM_CTRL_DECL(ename, id) \ + SOC_VALUE_ENUM_WIDE_DECL(ename##_enum, MUX_REG(id), 0, \ + tegra210_ahub_mux_texts, \ + tegra210_ahub_mux_values); \ + static const struct snd_kcontrol_new ename##_control = \ + SOC_DAPM_ENUM_EXT("Route", ename##_enum, \ + tegra_ahub_get_value_enum, \ + tegra_ahub_put_value_enum) + +#define MUX_ENUM_CTRL_DECL_186(ename, id) \ + SOC_VALUE_ENUM_WIDE_DECL(ename##_enum, MUX_REG(id), 0, \ + tegra186_ahub_mux_texts, \ + tegra186_ahub_mux_values); \ + static const struct snd_kcontrol_new ename##_control = \ + SOC_DAPM_ENUM_EXT("Route", ename##_enum, \ + tegra_ahub_get_value_enum, \ + tegra_ahub_put_value_enum) + +#define WIDGETS(sname, ename) \ + SND_SOC_DAPM_AIF_IN(sname " XBAR-RX", NULL, 0, SND_SOC_NOPM, 0, 0), \ + SND_SOC_DAPM_AIF_OUT(sname " XBAR-TX", NULL, 0, SND_SOC_NOPM, 0, 0), \ + SND_SOC_DAPM_MUX(sname " Mux", SND_SOC_NOPM, 0, 0, \ + &ename##_control) + +#define TX_WIDGETS(sname) \ + SND_SOC_DAPM_AIF_IN(sname " XBAR-RX", NULL, 0, SND_SOC_NOPM, 0, 0), \ + SND_SOC_DAPM_AIF_OUT(sname " XBAR-TX", NULL, 0, SND_SOC_NOPM, 0, 0) + +#define DAI(sname) \ + { \ + .name = "XBAR-" #sname, \ + .playback = { \ + .stream_name = #sname " XBAR-Playback", \ + .channels_min = 1, \ + .channels_max = 16, \ + .rates = SNDRV_PCM_RATE_8000_192000, \ + .formats = SNDRV_PCM_FMTBIT_S8 | \ + SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE, \ + }, \ + .capture = { \ + .stream_name = #sname " XBAR-Capture", \ + .channels_min = 1, \ + .channels_max = 16, \ + .rates = SNDRV_PCM_RATE_8000_192000, \ + .formats = SNDRV_PCM_FMTBIT_S8 | \ + SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE, \ + }, \ + } + +struct tegra_ahub_soc_data { + const struct regmap_config *regmap_config; + const struct snd_soc_component_driver *cmpnt_drv; + struct snd_soc_dai_driver *dai_drv; + unsigned int mask[4]; + unsigned int reg_count; + unsigned int num_dais; +}; + +struct tegra_ahub { + const struct tegra_ahub_soc_data *soc_data; + struct regmap *regmap; + struct clk *clk; +}; + +#endif diff --git a/sound/soc/tegra/tegra210_dmic.c b/sound/soc/tegra/tegra210_dmic.c new file mode 100644 index 000000000..dd3481ae3 --- /dev/null +++ b/sound/soc/tegra/tegra210_dmic.c @@ -0,0 +1,570 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// tegra210_dmic.c - Tegra210 DMIC driver +// +// Copyright (c) 2020 NVIDIA CORPORATION. All rights reserved. + +#include <linux/clk.h> +#include <linux/device.h> +#include <linux/math64.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <sound/core.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include "tegra210_dmic.h" +#include "tegra_cif.h" + +static const struct reg_default tegra210_dmic_reg_defaults[] = { + { TEGRA210_DMIC_TX_INT_MASK, 0x00000001 }, + { TEGRA210_DMIC_TX_CIF_CTRL, 0x00007700 }, + { TEGRA210_DMIC_CG, 0x1 }, + { TEGRA210_DMIC_CTRL, 0x00000301 }, + /* Below enables all filters - DCR, LP and SC */ + { TEGRA210_DMIC_DBG_CTRL, 0xe }, + /* Below as per latest POR value */ + { TEGRA210_DMIC_DCR_BIQUAD_0_COEF_4, 0x0 }, + /* LP filter is configured for pass through and used to apply gain */ + { TEGRA210_DMIC_LP_BIQUAD_0_COEF_0, 0x00800000 }, + { TEGRA210_DMIC_LP_BIQUAD_0_COEF_1, 0x0 }, + { TEGRA210_DMIC_LP_BIQUAD_0_COEF_2, 0x0 }, + { TEGRA210_DMIC_LP_BIQUAD_0_COEF_3, 0x0 }, + { TEGRA210_DMIC_LP_BIQUAD_0_COEF_4, 0x0 }, + { TEGRA210_DMIC_LP_BIQUAD_1_COEF_0, 0x00800000 }, + { TEGRA210_DMIC_LP_BIQUAD_1_COEF_1, 0x0 }, + { TEGRA210_DMIC_LP_BIQUAD_1_COEF_2, 0x0 }, + { TEGRA210_DMIC_LP_BIQUAD_1_COEF_3, 0x0 }, + { TEGRA210_DMIC_LP_BIQUAD_1_COEF_4, 0x0 }, +}; + +static int __maybe_unused tegra210_dmic_runtime_suspend(struct device *dev) +{ + struct tegra210_dmic *dmic = dev_get_drvdata(dev); + + regcache_cache_only(dmic->regmap, true); + regcache_mark_dirty(dmic->regmap); + + clk_disable_unprepare(dmic->clk_dmic); + + return 0; +} + +static int __maybe_unused tegra210_dmic_runtime_resume(struct device *dev) +{ + struct tegra210_dmic *dmic = dev_get_drvdata(dev); + int err; + + err = clk_prepare_enable(dmic->clk_dmic); + if (err) { + dev_err(dev, "failed to enable DMIC clock, err: %d\n", err); + return err; + } + + regcache_cache_only(dmic->regmap, false); + regcache_sync(dmic->regmap); + + return 0; +} + +static int tegra210_dmic_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct tegra210_dmic *dmic = snd_soc_dai_get_drvdata(dai); + unsigned int srate, clk_rate, channels; + struct tegra_cif_conf cif_conf; + unsigned long long gain_q23 = DEFAULT_GAIN_Q23; + int err; + + memset(&cif_conf, 0, sizeof(struct tegra_cif_conf)); + + channels = params_channels(params); + + cif_conf.audio_ch = channels; + + switch (dmic->ch_select) { + case DMIC_CH_SELECT_LEFT: + case DMIC_CH_SELECT_RIGHT: + cif_conf.client_ch = 1; + break; + case DMIC_CH_SELECT_STEREO: + cif_conf.client_ch = 2; + break; + default: + dev_err(dai->dev, "invalid DMIC client channels\n"); + return -EINVAL; + } + + srate = params_rate(params); + + /* + * DMIC clock rate is a multiple of 'Over Sampling Ratio' and + * 'Sample Rate'. The supported OSR values are 64, 128 and 256. + */ + clk_rate = (DMIC_OSR_FACTOR << dmic->osr_val) * srate; + + err = clk_set_rate(dmic->clk_dmic, clk_rate); + if (err) { + dev_err(dai->dev, "can't set DMIC clock rate %u, err: %d\n", + clk_rate, err); + return err; + } + + regmap_update_bits(dmic->regmap, + /* Reg */ + TEGRA210_DMIC_CTRL, + /* Mask */ + TEGRA210_DMIC_CTRL_LRSEL_POLARITY_MASK | + TEGRA210_DMIC_CTRL_OSR_MASK | + TEGRA210_DMIC_CTRL_CHANNEL_SELECT_MASK, + /* Value */ + (dmic->lrsel << LRSEL_POL_SHIFT) | + (dmic->osr_val << OSR_SHIFT) | + ((dmic->ch_select + 1) << CH_SEL_SHIFT)); + + /* + * Use LP filter gain register to apply boost. + * Boost Gain Volume control has 100x factor. + */ + if (dmic->boost_gain) + gain_q23 = div_u64(gain_q23 * dmic->boost_gain, 100); + + regmap_write(dmic->regmap, TEGRA210_DMIC_LP_FILTER_GAIN, + (unsigned int)gain_q23); + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + cif_conf.audio_bits = TEGRA_ACIF_BITS_16; + break; + case SNDRV_PCM_FORMAT_S32_LE: + cif_conf.audio_bits = TEGRA_ACIF_BITS_32; + break; + default: + dev_err(dai->dev, "unsupported format!\n"); + return -EOPNOTSUPP; + } + + cif_conf.client_bits = TEGRA_ACIF_BITS_24; + cif_conf.mono_conv = dmic->mono_to_stereo; + cif_conf.stereo_conv = dmic->stereo_to_mono; + + tegra_set_cif(dmic->regmap, TEGRA210_DMIC_TX_CIF_CTRL, &cif_conf); + + return 0; +} + +static int tegra210_dmic_get_boost_gain(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_soc_kcontrol_component(kcontrol); + struct tegra210_dmic *dmic = snd_soc_component_get_drvdata(comp); + + ucontrol->value.integer.value[0] = dmic->boost_gain; + + return 0; +} + +static int tegra210_dmic_put_boost_gain(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_soc_kcontrol_component(kcontrol); + struct tegra210_dmic *dmic = snd_soc_component_get_drvdata(comp); + int value = ucontrol->value.integer.value[0]; + + if (value == dmic->boost_gain) + return 0; + + dmic->boost_gain = value; + + return 1; +} + +static int tegra210_dmic_get_ch_select(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_soc_kcontrol_component(kcontrol); + struct tegra210_dmic *dmic = snd_soc_component_get_drvdata(comp); + + ucontrol->value.enumerated.item[0] = dmic->ch_select; + + return 0; +} + +static int tegra210_dmic_put_ch_select(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_soc_kcontrol_component(kcontrol); + struct tegra210_dmic *dmic = snd_soc_component_get_drvdata(comp); + unsigned int value = ucontrol->value.enumerated.item[0]; + + if (value == dmic->ch_select) + return 0; + + dmic->ch_select = value; + + return 1; +} + +static int tegra210_dmic_get_mono_to_stereo(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_soc_kcontrol_component(kcontrol); + struct tegra210_dmic *dmic = snd_soc_component_get_drvdata(comp); + + ucontrol->value.enumerated.item[0] = dmic->mono_to_stereo; + + return 0; +} + +static int tegra210_dmic_put_mono_to_stereo(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_soc_kcontrol_component(kcontrol); + struct tegra210_dmic *dmic = snd_soc_component_get_drvdata(comp); + unsigned int value = ucontrol->value.enumerated.item[0]; + + if (value == dmic->mono_to_stereo) + return 0; + + dmic->mono_to_stereo = value; + + return 1; +} + +static int tegra210_dmic_get_stereo_to_mono(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_soc_kcontrol_component(kcontrol); + struct tegra210_dmic *dmic = snd_soc_component_get_drvdata(comp); + + ucontrol->value.enumerated.item[0] = dmic->stereo_to_mono; + + return 0; +} + +static int tegra210_dmic_put_stereo_to_mono(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_soc_kcontrol_component(kcontrol); + struct tegra210_dmic *dmic = snd_soc_component_get_drvdata(comp); + unsigned int value = ucontrol->value.enumerated.item[0]; + + if (value == dmic->stereo_to_mono) + return 0; + + dmic->stereo_to_mono = value; + + return 1; +} + +static int tegra210_dmic_get_osr_val(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_soc_kcontrol_component(kcontrol); + struct tegra210_dmic *dmic = snd_soc_component_get_drvdata(comp); + + ucontrol->value.enumerated.item[0] = dmic->osr_val; + + return 0; +} + +static int tegra210_dmic_put_osr_val(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_soc_kcontrol_component(kcontrol); + struct tegra210_dmic *dmic = snd_soc_component_get_drvdata(comp); + unsigned int value = ucontrol->value.enumerated.item[0]; + + if (value == dmic->osr_val) + return 0; + + dmic->osr_val = value; + + return 1; +} + +static int tegra210_dmic_get_pol_sel(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_soc_kcontrol_component(kcontrol); + struct tegra210_dmic *dmic = snd_soc_component_get_drvdata(comp); + + ucontrol->value.enumerated.item[0] = dmic->lrsel; + + return 0; +} + +static int tegra210_dmic_put_pol_sel(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_soc_kcontrol_component(kcontrol); + struct tegra210_dmic *dmic = snd_soc_component_get_drvdata(comp); + unsigned int value = ucontrol->value.enumerated.item[0]; + + if (value == dmic->lrsel) + return 0; + + dmic->lrsel = value; + + return 1; +} + +static const struct snd_soc_dai_ops tegra210_dmic_dai_ops = { + .hw_params = tegra210_dmic_hw_params, +}; + +static struct snd_soc_dai_driver tegra210_dmic_dais[] = { + { + .name = "DMIC-CIF", + .capture = { + .stream_name = "CIF-Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S32_LE, + }, + }, + { + .name = "DMIC-DAP", + .capture = { + .stream_name = "DAP-Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S32_LE, + }, + .ops = &tegra210_dmic_dai_ops, + .symmetric_rates = 1, + }, +}; + +static const struct snd_soc_dapm_widget tegra210_dmic_widgets[] = { + SND_SOC_DAPM_AIF_OUT("TX", NULL, 0, TEGRA210_DMIC_ENABLE, 0, 0), + SND_SOC_DAPM_MIC("MIC", NULL), +}; + +static const struct snd_soc_dapm_route tegra210_dmic_routes[] = { + { "XBAR-RX", NULL, "XBAR-Capture" }, + { "XBAR-Capture", NULL, "CIF-Capture" }, + { "CIF-Capture", NULL, "TX" }, + { "TX", NULL, "DAP-Capture" }, + { "DAP-Capture", NULL, "MIC" }, +}; + +static const char * const tegra210_dmic_ch_select[] = { + "Left", "Right", "Stereo", +}; + +static const struct soc_enum tegra210_dmic_ch_enum = + SOC_ENUM_SINGLE(0, 0, ARRAY_SIZE(tegra210_dmic_ch_select), + tegra210_dmic_ch_select); + +static const char * const tegra210_dmic_mono_conv_text[] = { + "Zero", "Copy", +}; + +static const char * const tegra210_dmic_stereo_conv_text[] = { + "CH0", "CH1", "AVG", +}; + +static const struct soc_enum tegra210_dmic_mono_conv_enum = + SOC_ENUM_SINGLE(0, 0, ARRAY_SIZE(tegra210_dmic_mono_conv_text), + tegra210_dmic_mono_conv_text); + +static const struct soc_enum tegra210_dmic_stereo_conv_enum = + SOC_ENUM_SINGLE(0, 0, ARRAY_SIZE(tegra210_dmic_stereo_conv_text), + tegra210_dmic_stereo_conv_text); + +static const char * const tegra210_dmic_osr_text[] = { + "OSR_64", "OSR_128", "OSR_256", +}; + +static const struct soc_enum tegra210_dmic_osr_enum = + SOC_ENUM_SINGLE(0, 0, ARRAY_SIZE(tegra210_dmic_osr_text), + tegra210_dmic_osr_text); + +static const char * const tegra210_dmic_lrsel_text[] = { + "Left", "Right", +}; + +static const struct soc_enum tegra210_dmic_lrsel_enum = + SOC_ENUM_SINGLE(0, 0, ARRAY_SIZE(tegra210_dmic_lrsel_text), + tegra210_dmic_lrsel_text); + +static const struct snd_kcontrol_new tegra210_dmic_controls[] = { + SOC_SINGLE_EXT("Boost Gain Volume", 0, 0, MAX_BOOST_GAIN, 0, + tegra210_dmic_get_boost_gain, + tegra210_dmic_put_boost_gain), + SOC_ENUM_EXT("Channel Select", tegra210_dmic_ch_enum, + tegra210_dmic_get_ch_select, tegra210_dmic_put_ch_select), + SOC_ENUM_EXT("Mono To Stereo", + tegra210_dmic_mono_conv_enum, + tegra210_dmic_get_mono_to_stereo, + tegra210_dmic_put_mono_to_stereo), + SOC_ENUM_EXT("Stereo To Mono", + tegra210_dmic_stereo_conv_enum, + tegra210_dmic_get_stereo_to_mono, + tegra210_dmic_put_stereo_to_mono), + SOC_ENUM_EXT("OSR Value", tegra210_dmic_osr_enum, + tegra210_dmic_get_osr_val, tegra210_dmic_put_osr_val), + SOC_ENUM_EXT("LR Polarity Select", tegra210_dmic_lrsel_enum, + tegra210_dmic_get_pol_sel, tegra210_dmic_put_pol_sel), +}; + +static const struct snd_soc_component_driver tegra210_dmic_compnt = { + .dapm_widgets = tegra210_dmic_widgets, + .num_dapm_widgets = ARRAY_SIZE(tegra210_dmic_widgets), + .dapm_routes = tegra210_dmic_routes, + .num_dapm_routes = ARRAY_SIZE(tegra210_dmic_routes), + .controls = tegra210_dmic_controls, + .num_controls = ARRAY_SIZE(tegra210_dmic_controls), +}; + +static bool tegra210_dmic_wr_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TEGRA210_DMIC_TX_INT_MASK ... TEGRA210_DMIC_TX_CIF_CTRL: + case TEGRA210_DMIC_ENABLE ... TEGRA210_DMIC_CG: + case TEGRA210_DMIC_CTRL: + case TEGRA210_DMIC_DBG_CTRL: + case TEGRA210_DMIC_DCR_BIQUAD_0_COEF_4 ... TEGRA210_DMIC_LP_BIQUAD_1_COEF_4: + return true; + default: + return false; + }; +} + +static bool tegra210_dmic_rd_reg(struct device *dev, unsigned int reg) +{ + if (tegra210_dmic_wr_reg(dev, reg)) + return true; + + switch (reg) { + case TEGRA210_DMIC_TX_STATUS: + case TEGRA210_DMIC_TX_INT_STATUS: + case TEGRA210_DMIC_STATUS: + case TEGRA210_DMIC_INT_STATUS: + return true; + default: + return false; + }; +} + +static bool tegra210_dmic_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TEGRA210_DMIC_TX_STATUS: + case TEGRA210_DMIC_TX_INT_STATUS: + case TEGRA210_DMIC_TX_INT_SET: + case TEGRA210_DMIC_SOFT_RESET: + case TEGRA210_DMIC_STATUS: + case TEGRA210_DMIC_INT_STATUS: + return true; + default: + return false; + }; +} + +static const struct regmap_config tegra210_dmic_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = TEGRA210_DMIC_LP_BIQUAD_1_COEF_4, + .writeable_reg = tegra210_dmic_wr_reg, + .readable_reg = tegra210_dmic_rd_reg, + .volatile_reg = tegra210_dmic_volatile_reg, + .reg_defaults = tegra210_dmic_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(tegra210_dmic_reg_defaults), + .cache_type = REGCACHE_FLAT, +}; + +static int tegra210_dmic_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct tegra210_dmic *dmic; + void __iomem *regs; + int err; + + dmic = devm_kzalloc(dev, sizeof(*dmic), GFP_KERNEL); + if (!dmic) + return -ENOMEM; + + dmic->osr_val = DMIC_OSR_64; + dmic->ch_select = DMIC_CH_SELECT_STEREO; + dmic->lrsel = DMIC_LRSEL_LEFT; + dmic->boost_gain = 0; + dmic->stereo_to_mono = 0; /* "CH0" */ + + dev_set_drvdata(dev, dmic); + + dmic->clk_dmic = devm_clk_get(dev, "dmic"); + if (IS_ERR(dmic->clk_dmic)) { + dev_err(dev, "can't retrieve DMIC clock\n"); + return PTR_ERR(dmic->clk_dmic); + } + + regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + dmic->regmap = devm_regmap_init_mmio(dev, regs, + &tegra210_dmic_regmap_config); + if (IS_ERR(dmic->regmap)) { + dev_err(dev, "regmap init failed\n"); + return PTR_ERR(dmic->regmap); + } + + regcache_cache_only(dmic->regmap, true); + + err = devm_snd_soc_register_component(dev, &tegra210_dmic_compnt, + tegra210_dmic_dais, + ARRAY_SIZE(tegra210_dmic_dais)); + if (err) { + dev_err(dev, "can't register DMIC component, err: %d\n", err); + return err; + } + + pm_runtime_enable(dev); + + return 0; +} + +static int tegra210_dmic_remove(struct platform_device *pdev) +{ + pm_runtime_disable(&pdev->dev); + + return 0; +} + +static const struct dev_pm_ops tegra210_dmic_pm_ops = { + SET_RUNTIME_PM_OPS(tegra210_dmic_runtime_suspend, + tegra210_dmic_runtime_resume, NULL) + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) +}; + +static const struct of_device_id tegra210_dmic_of_match[] = { + { .compatible = "nvidia,tegra210-dmic" }, + {}, +}; +MODULE_DEVICE_TABLE(of, tegra210_dmic_of_match); + +static struct platform_driver tegra210_dmic_driver = { + .driver = { + .name = "tegra210-dmic", + .of_match_table = tegra210_dmic_of_match, + .pm = &tegra210_dmic_pm_ops, + }, + .probe = tegra210_dmic_probe, + .remove = tegra210_dmic_remove, +}; +module_platform_driver(tegra210_dmic_driver) + +MODULE_AUTHOR("Rahul Mittal <rmittal@nvidia.com>"); +MODULE_DESCRIPTION("Tegra210 ASoC DMIC driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/tegra/tegra210_dmic.h b/sound/soc/tegra/tegra210_dmic.h new file mode 100644 index 000000000..6418c223b --- /dev/null +++ b/sound/soc/tegra/tegra210_dmic.h @@ -0,0 +1,82 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * tegra210_dmic.h - Definitions for Tegra210 DMIC driver + * + * Copyright (c) 2020 NVIDIA CORPORATION. All rights reserved. + * + */ + +#ifndef __TEGRA210_DMIC_H__ +#define __TEGRA210_DMIC_H__ + +/* Register offsets from DMIC BASE */ +#define TEGRA210_DMIC_TX_STATUS 0x0c +#define TEGRA210_DMIC_TX_INT_STATUS 0x10 +#define TEGRA210_DMIC_TX_INT_MASK 0x14 +#define TEGRA210_DMIC_TX_INT_SET 0x18 +#define TEGRA210_DMIC_TX_INT_CLEAR 0x1c +#define TEGRA210_DMIC_TX_CIF_CTRL 0x20 +#define TEGRA210_DMIC_ENABLE 0x40 +#define TEGRA210_DMIC_SOFT_RESET 0x44 +#define TEGRA210_DMIC_CG 0x48 +#define TEGRA210_DMIC_STATUS 0x4c +#define TEGRA210_DMIC_INT_STATUS 0x50 +#define TEGRA210_DMIC_CTRL 0x64 +#define TEGRA210_DMIC_DBG_CTRL 0x70 +#define TEGRA210_DMIC_DCR_BIQUAD_0_COEF_4 0x88 +#define TEGRA210_DMIC_LP_FILTER_GAIN 0x8c +#define TEGRA210_DMIC_LP_BIQUAD_0_COEF_0 0x90 +#define TEGRA210_DMIC_LP_BIQUAD_0_COEF_1 0x94 +#define TEGRA210_DMIC_LP_BIQUAD_0_COEF_2 0x98 +#define TEGRA210_DMIC_LP_BIQUAD_0_COEF_3 0x9c +#define TEGRA210_DMIC_LP_BIQUAD_0_COEF_4 0xa0 +#define TEGRA210_DMIC_LP_BIQUAD_1_COEF_0 0xa4 +#define TEGRA210_DMIC_LP_BIQUAD_1_COEF_1 0xa8 +#define TEGRA210_DMIC_LP_BIQUAD_1_COEF_2 0xac +#define TEGRA210_DMIC_LP_BIQUAD_1_COEF_3 0xb0 +#define TEGRA210_DMIC_LP_BIQUAD_1_COEF_4 0xb4 + +/* Fields in TEGRA210_DMIC_CTRL */ +#define CH_SEL_SHIFT 8 +#define TEGRA210_DMIC_CTRL_CHANNEL_SELECT_MASK (0x3 << CH_SEL_SHIFT) +#define LRSEL_POL_SHIFT 4 +#define TEGRA210_DMIC_CTRL_LRSEL_POLARITY_MASK (0x1 << LRSEL_POL_SHIFT) +#define OSR_SHIFT 0 +#define TEGRA210_DMIC_CTRL_OSR_MASK (0x3 << OSR_SHIFT) + +#define DMIC_OSR_FACTOR 64 + +#define DEFAULT_GAIN_Q23 0x800000 + +/* Max boost gain factor used for mixer control */ +#define MAX_BOOST_GAIN 25599 + +enum tegra_dmic_ch_select { + DMIC_CH_SELECT_LEFT, + DMIC_CH_SELECT_RIGHT, + DMIC_CH_SELECT_STEREO, +}; + +enum tegra_dmic_osr { + DMIC_OSR_64, + DMIC_OSR_128, + DMIC_OSR_256, +}; + +enum tegra_dmic_lrsel { + DMIC_LRSEL_LEFT, + DMIC_LRSEL_RIGHT, +}; + +struct tegra210_dmic { + struct clk *clk_dmic; + struct regmap *regmap; + unsigned int mono_to_stereo; + unsigned int stereo_to_mono; + unsigned int boost_gain; + unsigned int ch_select; + unsigned int osr_val; + unsigned int lrsel; +}; + +#endif diff --git a/sound/soc/tegra/tegra210_i2s.c b/sound/soc/tegra/tegra210_i2s.c new file mode 100644 index 000000000..33fb37ad1 --- /dev/null +++ b/sound/soc/tegra/tegra210_i2s.c @@ -0,0 +1,968 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// tegra210_i2s.c - Tegra210 I2S driver +// +// Copyright (c) 2020 NVIDIA CORPORATION. All rights reserved. + +#include <linux/clk.h> +#include <linux/device.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <sound/core.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include "tegra210_i2s.h" +#include "tegra_cif.h" + +static const struct reg_default tegra210_i2s_reg_defaults[] = { + { TEGRA210_I2S_RX_INT_MASK, 0x00000003 }, + { TEGRA210_I2S_RX_CIF_CTRL, 0x00007700 }, + { TEGRA210_I2S_TX_INT_MASK, 0x00000003 }, + { TEGRA210_I2S_TX_CIF_CTRL, 0x00007700 }, + { TEGRA210_I2S_CG, 0x1 }, + { TEGRA210_I2S_TIMING, 0x0000001f }, + { TEGRA210_I2S_ENABLE, 0x1 }, + /* + * Below update does not have any effect on Tegra186 and Tegra194. + * On Tegra210, I2S4 has "i2s4a" and "i2s4b" pins and below update + * is required to select i2s4b for it to be functional for I2S + * operation. + */ + { TEGRA210_I2S_CYA, 0x1 }, +}; + +static void tegra210_i2s_set_slot_ctrl(struct regmap *regmap, + unsigned int total_slots, + unsigned int tx_slot_mask, + unsigned int rx_slot_mask) +{ + regmap_write(regmap, TEGRA210_I2S_SLOT_CTRL, total_slots - 1); + regmap_write(regmap, TEGRA210_I2S_TX_SLOT_CTRL, tx_slot_mask); + regmap_write(regmap, TEGRA210_I2S_RX_SLOT_CTRL, rx_slot_mask); +} + +static int tegra210_i2s_set_clock_rate(struct device *dev, + unsigned int clock_rate) +{ + struct tegra210_i2s *i2s = dev_get_drvdata(dev); + unsigned int val; + int err; + + regmap_read(i2s->regmap, TEGRA210_I2S_CTRL, &val); + + /* No need to set rates if I2S is being operated in slave */ + if (!(val & I2S_CTRL_MASTER_EN)) + return 0; + + err = clk_set_rate(i2s->clk_i2s, clock_rate); + if (err) { + dev_err(dev, "can't set I2S bit clock rate %u, err: %d\n", + clock_rate, err); + return err; + } + + if (!IS_ERR(i2s->clk_sync_input)) { + /* + * Other I/O modules in AHUB can use i2s bclk as reference + * clock. Below sets sync input clock rate as per bclk, + * which can be used as input to other I/O modules. + */ + err = clk_set_rate(i2s->clk_sync_input, clock_rate); + if (err) { + dev_err(dev, + "can't set I2S sync input rate %u, err = %d\n", + clock_rate, err); + return err; + } + } + + return 0; +} + +static int tegra210_i2s_sw_reset(struct snd_soc_component *compnt, + bool is_playback) +{ + struct device *dev = compnt->dev; + struct tegra210_i2s *i2s = dev_get_drvdata(dev); + unsigned int reset_mask = I2S_SOFT_RESET_MASK; + unsigned int reset_en = I2S_SOFT_RESET_EN; + unsigned int reset_reg, cif_reg, stream_reg; + unsigned int cif_ctrl, stream_ctrl, i2s_ctrl, val; + int err; + + if (is_playback) { + reset_reg = TEGRA210_I2S_RX_SOFT_RESET; + cif_reg = TEGRA210_I2S_RX_CIF_CTRL; + stream_reg = TEGRA210_I2S_RX_CTRL; + } else { + reset_reg = TEGRA210_I2S_TX_SOFT_RESET; + cif_reg = TEGRA210_I2S_TX_CIF_CTRL; + stream_reg = TEGRA210_I2S_TX_CTRL; + } + + /* Store CIF and I2S control values */ + regmap_read(i2s->regmap, cif_reg, &cif_ctrl); + regmap_read(i2s->regmap, stream_reg, &stream_ctrl); + regmap_read(i2s->regmap, TEGRA210_I2S_CTRL, &i2s_ctrl); + + /* Reset to make sure the previous transactions are clean */ + regmap_update_bits(i2s->regmap, reset_reg, reset_mask, reset_en); + + err = regmap_read_poll_timeout(i2s->regmap, reset_reg, val, + !(val & reset_mask & reset_en), + 10, 10000); + if (err) { + dev_err(dev, "timeout: failed to reset I2S for %s\n", + is_playback ? "playback" : "capture"); + return err; + } + + /* Restore CIF and I2S control values */ + regmap_write(i2s->regmap, cif_reg, cif_ctrl); + regmap_write(i2s->regmap, stream_reg, stream_ctrl); + regmap_write(i2s->regmap, TEGRA210_I2S_CTRL, i2s_ctrl); + + return 0; +} + +static int tegra210_i2s_init(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *compnt = snd_soc_dapm_to_component(w->dapm); + struct device *dev = compnt->dev; + struct tegra210_i2s *i2s = dev_get_drvdata(dev); + unsigned int val, status_reg; + bool is_playback; + int err; + + switch (w->reg) { + case TEGRA210_I2S_RX_ENABLE: + is_playback = true; + status_reg = TEGRA210_I2S_RX_STATUS; + break; + case TEGRA210_I2S_TX_ENABLE: + is_playback = false; + status_reg = TEGRA210_I2S_TX_STATUS; + break; + default: + return -EINVAL; + } + + /* Ensure I2S is in disabled state before new session */ + err = regmap_read_poll_timeout(i2s->regmap, status_reg, val, + !(val & I2S_EN_MASK & I2S_EN), + 10, 10000); + if (err) { + dev_err(dev, "timeout: previous I2S %s is still active\n", + is_playback ? "playback" : "capture"); + return err; + } + + return tegra210_i2s_sw_reset(compnt, is_playback); +} + +static int __maybe_unused tegra210_i2s_runtime_suspend(struct device *dev) +{ + struct tegra210_i2s *i2s = dev_get_drvdata(dev); + + regcache_cache_only(i2s->regmap, true); + regcache_mark_dirty(i2s->regmap); + + clk_disable_unprepare(i2s->clk_i2s); + + return 0; +} + +static int __maybe_unused tegra210_i2s_runtime_resume(struct device *dev) +{ + struct tegra210_i2s *i2s = dev_get_drvdata(dev); + int err; + + err = clk_prepare_enable(i2s->clk_i2s); + if (err) { + dev_err(dev, "failed to enable I2S bit clock, err: %d\n", err); + return err; + } + + regcache_cache_only(i2s->regmap, false); + regcache_sync(i2s->regmap); + + return 0; +} + +static void tegra210_i2s_set_data_offset(struct tegra210_i2s *i2s, + unsigned int data_offset) +{ + /* Capture path */ + regmap_update_bits(i2s->regmap, TEGRA210_I2S_TX_CTRL, + I2S_CTRL_DATA_OFFSET_MASK, + data_offset << I2S_DATA_SHIFT); + + /* Playback path */ + regmap_update_bits(i2s->regmap, TEGRA210_I2S_RX_CTRL, + I2S_CTRL_DATA_OFFSET_MASK, + data_offset << I2S_DATA_SHIFT); +} + +static int tegra210_i2s_set_fmt(struct snd_soc_dai *dai, + unsigned int fmt) +{ + struct tegra210_i2s *i2s = snd_soc_dai_get_drvdata(dai); + unsigned int mask, val; + + mask = I2S_CTRL_MASTER_EN_MASK; + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + val = 0; + break; + case SND_SOC_DAIFMT_CBM_CFM: + val = I2S_CTRL_MASTER_EN; + break; + default: + return -EINVAL; + } + + mask |= I2S_CTRL_FRAME_FMT_MASK | I2S_CTRL_LRCK_POL_MASK; + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_A: + val |= I2S_CTRL_FRAME_FMT_FSYNC_MODE; + val |= I2S_CTRL_LRCK_POL_HIGH; + tegra210_i2s_set_data_offset(i2s, 1); + break; + case SND_SOC_DAIFMT_DSP_B: + val |= I2S_CTRL_FRAME_FMT_FSYNC_MODE; + val |= I2S_CTRL_LRCK_POL_HIGH; + tegra210_i2s_set_data_offset(i2s, 0); + break; + /* I2S mode has data offset of 1 */ + case SND_SOC_DAIFMT_I2S: + val |= I2S_CTRL_FRAME_FMT_LRCK_MODE; + val |= I2S_CTRL_LRCK_POL_LOW; + tegra210_i2s_set_data_offset(i2s, 1); + break; + /* + * For RJ mode data offset is dependent on the sample size + * and the bclk ratio, and so is set when hw_params is called. + */ + case SND_SOC_DAIFMT_RIGHT_J: + val |= I2S_CTRL_FRAME_FMT_LRCK_MODE; + val |= I2S_CTRL_LRCK_POL_HIGH; + break; + case SND_SOC_DAIFMT_LEFT_J: + val |= I2S_CTRL_FRAME_FMT_LRCK_MODE; + val |= I2S_CTRL_LRCK_POL_HIGH; + tegra210_i2s_set_data_offset(i2s, 0); + break; + default: + return -EINVAL; + } + + mask |= I2S_CTRL_EDGE_CTRL_MASK; + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + val |= I2S_CTRL_EDGE_CTRL_POS_EDGE; + break; + case SND_SOC_DAIFMT_NB_IF: + val |= I2S_CTRL_EDGE_CTRL_POS_EDGE; + val ^= I2S_CTRL_LRCK_POL_MASK; + break; + case SND_SOC_DAIFMT_IB_NF: + val |= I2S_CTRL_EDGE_CTRL_NEG_EDGE; + break; + case SND_SOC_DAIFMT_IB_IF: + val |= I2S_CTRL_EDGE_CTRL_NEG_EDGE; + val ^= I2S_CTRL_LRCK_POL_MASK; + break; + default: + return -EINVAL; + } + + regmap_update_bits(i2s->regmap, TEGRA210_I2S_CTRL, mask, val); + + i2s->dai_fmt = fmt & SND_SOC_DAIFMT_FORMAT_MASK; + + return 0; +} + +static int tegra210_i2s_set_tdm_slot(struct snd_soc_dai *dai, + unsigned int tx_mask, unsigned int rx_mask, + int slots, int slot_width) +{ + struct tegra210_i2s *i2s = snd_soc_dai_get_drvdata(dai); + + /* Copy the required tx and rx mask */ + i2s->tx_mask = (tx_mask > DEFAULT_I2S_SLOT_MASK) ? + DEFAULT_I2S_SLOT_MASK : tx_mask; + i2s->rx_mask = (rx_mask > DEFAULT_I2S_SLOT_MASK) ? + DEFAULT_I2S_SLOT_MASK : rx_mask; + + return 0; +} + +static int tegra210_i2s_get_loopback(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *compnt = snd_soc_kcontrol_component(kcontrol); + struct tegra210_i2s *i2s = snd_soc_component_get_drvdata(compnt); + + ucontrol->value.integer.value[0] = i2s->loopback; + + return 0; +} + +static int tegra210_i2s_put_loopback(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *compnt = snd_soc_kcontrol_component(kcontrol); + struct tegra210_i2s *i2s = snd_soc_component_get_drvdata(compnt); + int value = ucontrol->value.integer.value[0]; + + if (value == i2s->loopback) + return 0; + + i2s->loopback = value; + + regmap_update_bits(i2s->regmap, TEGRA210_I2S_CTRL, I2S_CTRL_LPBK_MASK, + i2s->loopback << I2S_CTRL_LPBK_SHIFT); + + return 1; +} + +static int tegra210_i2s_get_fsync_width(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *compnt = snd_soc_kcontrol_component(kcontrol); + struct tegra210_i2s *i2s = snd_soc_component_get_drvdata(compnt); + + ucontrol->value.integer.value[0] = i2s->fsync_width; + + return 0; +} + +static int tegra210_i2s_put_fsync_width(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *compnt = snd_soc_kcontrol_component(kcontrol); + struct tegra210_i2s *i2s = snd_soc_component_get_drvdata(compnt); + int value = ucontrol->value.integer.value[0]; + + if (value == i2s->fsync_width) + return 0; + + i2s->fsync_width = value; + + /* + * Frame sync width is used only for FSYNC modes and not + * applicable for LRCK modes. Reset value for this field is "0", + * which means the width is one bit clock wide. + * The width requirement may depend on the codec and in such + * cases mixer control is used to update custom values. A value + * of "N" here means, width is "N + 1" bit clock wide. + */ + regmap_update_bits(i2s->regmap, TEGRA210_I2S_CTRL, + I2S_CTRL_FSYNC_WIDTH_MASK, + i2s->fsync_width << I2S_FSYNC_WIDTH_SHIFT); + + return 1; +} + +static int tegra210_i2s_cget_stereo_to_mono(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *compnt = snd_soc_kcontrol_component(kcontrol); + struct tegra210_i2s *i2s = snd_soc_component_get_drvdata(compnt); + + ucontrol->value.enumerated.item[0] = i2s->stereo_to_mono[I2S_TX_PATH]; + + return 0; +} + +static int tegra210_i2s_cput_stereo_to_mono(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *compnt = snd_soc_kcontrol_component(kcontrol); + struct tegra210_i2s *i2s = snd_soc_component_get_drvdata(compnt); + unsigned int value = ucontrol->value.enumerated.item[0]; + + if (value == i2s->stereo_to_mono[I2S_TX_PATH]) + return 0; + + i2s->stereo_to_mono[I2S_TX_PATH] = value; + + return 1; +} + +static int tegra210_i2s_cget_mono_to_stereo(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *compnt = snd_soc_kcontrol_component(kcontrol); + struct tegra210_i2s *i2s = snd_soc_component_get_drvdata(compnt); + + ucontrol->value.enumerated.item[0] = i2s->mono_to_stereo[I2S_TX_PATH]; + + return 0; +} + +static int tegra210_i2s_cput_mono_to_stereo(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *compnt = snd_soc_kcontrol_component(kcontrol); + struct tegra210_i2s *i2s = snd_soc_component_get_drvdata(compnt); + unsigned int value = ucontrol->value.enumerated.item[0]; + + if (value == i2s->mono_to_stereo[I2S_TX_PATH]) + return 0; + + i2s->mono_to_stereo[I2S_TX_PATH] = value; + + return 1; +} + +static int tegra210_i2s_pget_stereo_to_mono(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *compnt = snd_soc_kcontrol_component(kcontrol); + struct tegra210_i2s *i2s = snd_soc_component_get_drvdata(compnt); + + ucontrol->value.enumerated.item[0] = i2s->stereo_to_mono[I2S_RX_PATH]; + + return 0; +} + +static int tegra210_i2s_pput_stereo_to_mono(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *compnt = snd_soc_kcontrol_component(kcontrol); + struct tegra210_i2s *i2s = snd_soc_component_get_drvdata(compnt); + unsigned int value = ucontrol->value.enumerated.item[0]; + + if (value == i2s->stereo_to_mono[I2S_RX_PATH]) + return 0; + + i2s->stereo_to_mono[I2S_RX_PATH] = value; + + return 1; +} + +static int tegra210_i2s_pget_mono_to_stereo(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *compnt = snd_soc_kcontrol_component(kcontrol); + struct tegra210_i2s *i2s = snd_soc_component_get_drvdata(compnt); + + ucontrol->value.enumerated.item[0] = i2s->mono_to_stereo[I2S_RX_PATH]; + + return 0; +} + +static int tegra210_i2s_pput_mono_to_stereo(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *compnt = snd_soc_kcontrol_component(kcontrol); + struct tegra210_i2s *i2s = snd_soc_component_get_drvdata(compnt); + unsigned int value = ucontrol->value.enumerated.item[0]; + + if (value == i2s->mono_to_stereo[I2S_RX_PATH]) + return 0; + + i2s->mono_to_stereo[I2S_RX_PATH] = value; + + return 1; +} + +static int tegra210_i2s_pget_fifo_th(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *compnt = snd_soc_kcontrol_component(kcontrol); + struct tegra210_i2s *i2s = snd_soc_component_get_drvdata(compnt); + + ucontrol->value.integer.value[0] = i2s->rx_fifo_th; + + return 0; +} + +static int tegra210_i2s_pput_fifo_th(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *compnt = snd_soc_kcontrol_component(kcontrol); + struct tegra210_i2s *i2s = snd_soc_component_get_drvdata(compnt); + int value = ucontrol->value.integer.value[0]; + + if (value == i2s->rx_fifo_th) + return 0; + + i2s->rx_fifo_th = value; + + return 1; +} + +static int tegra210_i2s_get_bclk_ratio(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *compnt = snd_soc_kcontrol_component(kcontrol); + struct tegra210_i2s *i2s = snd_soc_component_get_drvdata(compnt); + + ucontrol->value.integer.value[0] = i2s->bclk_ratio; + + return 0; +} + +static int tegra210_i2s_put_bclk_ratio(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *compnt = snd_soc_kcontrol_component(kcontrol); + struct tegra210_i2s *i2s = snd_soc_component_get_drvdata(compnt); + int value = ucontrol->value.integer.value[0]; + + if (value == i2s->bclk_ratio) + return 0; + + i2s->bclk_ratio = value; + + return 1; +} + +static int tegra210_i2s_set_dai_bclk_ratio(struct snd_soc_dai *dai, + unsigned int ratio) +{ + struct tegra210_i2s *i2s = snd_soc_dai_get_drvdata(dai); + + i2s->bclk_ratio = ratio; + + return 0; +} + +static int tegra210_i2s_set_timing_params(struct device *dev, + unsigned int sample_size, + unsigned int srate, + unsigned int channels) +{ + struct tegra210_i2s *i2s = dev_get_drvdata(dev); + unsigned int val, bit_count, bclk_rate, num_bclk = sample_size; + int err; + + if (i2s->bclk_ratio) + num_bclk *= i2s->bclk_ratio; + + if (i2s->dai_fmt == SND_SOC_DAIFMT_RIGHT_J) + tegra210_i2s_set_data_offset(i2s, num_bclk - sample_size); + + /* I2S bit clock rate */ + bclk_rate = srate * channels * num_bclk; + + err = tegra210_i2s_set_clock_rate(dev, bclk_rate); + if (err) { + dev_err(dev, "can't set I2S bit clock rate %u, err: %d\n", + bclk_rate, err); + return err; + } + + regmap_read(i2s->regmap, TEGRA210_I2S_CTRL, &val); + + /* + * For LRCK mode, channel bit count depends on number of bit clocks + * on the left channel, where as for FSYNC mode bit count depends on + * the number of bit clocks in both left and right channels for DSP + * mode or the number of bit clocks in one TDM frame. + * + */ + switch (val & I2S_CTRL_FRAME_FMT_MASK) { + case I2S_CTRL_FRAME_FMT_LRCK_MODE: + bit_count = (bclk_rate / (srate * 2)) - 1; + break; + case I2S_CTRL_FRAME_FMT_FSYNC_MODE: + bit_count = (bclk_rate / srate) - 1; + + tegra210_i2s_set_slot_ctrl(i2s->regmap, channels, + i2s->tx_mask, i2s->rx_mask); + break; + default: + dev_err(dev, "invalid I2S frame format\n"); + return -EINVAL; + } + + if (bit_count > I2S_TIMING_CH_BIT_CNT_MASK) { + dev_err(dev, "invalid I2S channel bit count %u\n", bit_count); + return -EINVAL; + } + + regmap_write(i2s->regmap, TEGRA210_I2S_TIMING, + bit_count << I2S_TIMING_CH_BIT_CNT_SHIFT); + + return 0; +} + +static int tegra210_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct device *dev = dai->dev; + struct tegra210_i2s *i2s = snd_soc_dai_get_drvdata(dai); + unsigned int sample_size, channels, srate, val, reg, path; + struct tegra_cif_conf cif_conf; + + memset(&cif_conf, 0, sizeof(struct tegra_cif_conf)); + + channels = params_channels(params); + if (channels < 1) { + dev_err(dev, "invalid I2S %d channel configuration\n", + channels); + return -EINVAL; + } + + cif_conf.audio_ch = channels; + cif_conf.client_ch = channels; + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S8: + val = I2S_BITS_8; + sample_size = 8; + cif_conf.audio_bits = TEGRA_ACIF_BITS_8; + cif_conf.client_bits = TEGRA_ACIF_BITS_8; + break; + case SNDRV_PCM_FORMAT_S16_LE: + val = I2S_BITS_16; + sample_size = 16; + cif_conf.audio_bits = TEGRA_ACIF_BITS_16; + cif_conf.client_bits = TEGRA_ACIF_BITS_16; + break; + case SNDRV_PCM_FORMAT_S32_LE: + val = I2S_BITS_32; + sample_size = 32; + cif_conf.audio_bits = TEGRA_ACIF_BITS_32; + cif_conf.client_bits = TEGRA_ACIF_BITS_32; + break; + default: + dev_err(dev, "unsupported format!\n"); + return -EOPNOTSUPP; + } + + /* Program sample size */ + regmap_update_bits(i2s->regmap, TEGRA210_I2S_CTRL, + I2S_CTRL_BIT_SIZE_MASK, val); + + srate = params_rate(params); + + /* For playback I2S RX-CIF and for capture TX-CIF is used */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + path = I2S_RX_PATH; + else + path = I2S_TX_PATH; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + unsigned int max_th; + + /* FIFO threshold in terms of frames */ + max_th = (I2S_RX_FIFO_DEPTH / cif_conf.audio_ch) - 1; + + if (i2s->rx_fifo_th > max_th) + i2s->rx_fifo_th = max_th; + + cif_conf.threshold = i2s->rx_fifo_th; + + reg = TEGRA210_I2S_RX_CIF_CTRL; + } else { + reg = TEGRA210_I2S_TX_CIF_CTRL; + } + + cif_conf.mono_conv = i2s->mono_to_stereo[path]; + cif_conf.stereo_conv = i2s->stereo_to_mono[path]; + + tegra_set_cif(i2s->regmap, reg, &cif_conf); + + return tegra210_i2s_set_timing_params(dev, sample_size, srate, + cif_conf.client_ch); +} + +static const struct snd_soc_dai_ops tegra210_i2s_dai_ops = { + .set_fmt = tegra210_i2s_set_fmt, + .hw_params = tegra210_i2s_hw_params, + .set_bclk_ratio = tegra210_i2s_set_dai_bclk_ratio, + .set_tdm_slot = tegra210_i2s_set_tdm_slot, +}; + +static struct snd_soc_dai_driver tegra210_i2s_dais[] = { + { + .name = "I2S-CIF", + .playback = { + .stream_name = "CIF-Playback", + .channels_min = 1, + .channels_max = 16, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = SNDRV_PCM_FMTBIT_S8 | + SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S32_LE, + }, + .capture = { + .stream_name = "CIF-Capture", + .channels_min = 1, + .channels_max = 16, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = SNDRV_PCM_FMTBIT_S8 | + SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S32_LE, + }, + }, + { + .name = "I2S-DAP", + .playback = { + .stream_name = "DAP-Playback", + .channels_min = 1, + .channels_max = 16, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = SNDRV_PCM_FMTBIT_S8 | + SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S32_LE, + }, + .capture = { + .stream_name = "DAP-Capture", + .channels_min = 1, + .channels_max = 16, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = SNDRV_PCM_FMTBIT_S8 | + SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S32_LE, + }, + .ops = &tegra210_i2s_dai_ops, + .symmetric_rates = 1, + }, +}; + +static const char * const tegra210_i2s_stereo_conv_text[] = { + "CH0", "CH1", "AVG", +}; + +static const char * const tegra210_i2s_mono_conv_text[] = { + "Zero", "Copy", +}; + +static const struct soc_enum tegra210_i2s_mono_conv_enum = + SOC_ENUM_SINGLE(0, 0, ARRAY_SIZE(tegra210_i2s_mono_conv_text), + tegra210_i2s_mono_conv_text); + +static const struct soc_enum tegra210_i2s_stereo_conv_enum = + SOC_ENUM_SINGLE(0, 0, ARRAY_SIZE(tegra210_i2s_stereo_conv_text), + tegra210_i2s_stereo_conv_text); + +static const struct snd_kcontrol_new tegra210_i2s_controls[] = { + SOC_SINGLE_EXT("Loopback", 0, 0, 1, 0, tegra210_i2s_get_loopback, + tegra210_i2s_put_loopback), + SOC_SINGLE_EXT("FSYNC Width", 0, 0, 255, 0, + tegra210_i2s_get_fsync_width, + tegra210_i2s_put_fsync_width), + SOC_ENUM_EXT("Capture Stereo To Mono", tegra210_i2s_stereo_conv_enum, + tegra210_i2s_cget_stereo_to_mono, + tegra210_i2s_cput_stereo_to_mono), + SOC_ENUM_EXT("Capture Mono To Stereo", tegra210_i2s_mono_conv_enum, + tegra210_i2s_cget_mono_to_stereo, + tegra210_i2s_cput_mono_to_stereo), + SOC_ENUM_EXT("Playback Stereo To Mono", tegra210_i2s_stereo_conv_enum, + tegra210_i2s_pget_mono_to_stereo, + tegra210_i2s_pput_mono_to_stereo), + SOC_ENUM_EXT("Playback Mono To Stereo", tegra210_i2s_mono_conv_enum, + tegra210_i2s_pget_stereo_to_mono, + tegra210_i2s_pput_stereo_to_mono), + SOC_SINGLE_EXT("Playback FIFO Threshold", 0, 0, I2S_RX_FIFO_DEPTH - 1, + 0, tegra210_i2s_pget_fifo_th, tegra210_i2s_pput_fifo_th), + SOC_SINGLE_EXT("BCLK Ratio", 0, 0, INT_MAX, 0, + tegra210_i2s_get_bclk_ratio, + tegra210_i2s_put_bclk_ratio), +}; + +static const struct snd_soc_dapm_widget tegra210_i2s_widgets[] = { + SND_SOC_DAPM_AIF_IN_E("RX", NULL, 0, TEGRA210_I2S_RX_ENABLE, + 0, 0, tegra210_i2s_init, SND_SOC_DAPM_PRE_PMU), + SND_SOC_DAPM_AIF_OUT_E("TX", NULL, 0, TEGRA210_I2S_TX_ENABLE, + 0, 0, tegra210_i2s_init, SND_SOC_DAPM_PRE_PMU), + SND_SOC_DAPM_MIC("MIC", NULL), + SND_SOC_DAPM_SPK("SPK", NULL), +}; + +static const struct snd_soc_dapm_route tegra210_i2s_routes[] = { + /* Playback route from XBAR */ + { "XBAR-Playback", NULL, "XBAR-TX" }, + { "CIF-Playback", NULL, "XBAR-Playback" }, + { "RX", NULL, "CIF-Playback" }, + { "DAP-Playback", NULL, "RX" }, + { "SPK", NULL, "DAP-Playback" }, + /* Capture route to XBAR */ + { "XBAR-RX", NULL, "XBAR-Capture" }, + { "XBAR-Capture", NULL, "CIF-Capture" }, + { "CIF-Capture", NULL, "TX" }, + { "TX", NULL, "DAP-Capture" }, + { "DAP-Capture", NULL, "MIC" }, +}; + +static const struct snd_soc_component_driver tegra210_i2s_cmpnt = { + .dapm_widgets = tegra210_i2s_widgets, + .num_dapm_widgets = ARRAY_SIZE(tegra210_i2s_widgets), + .dapm_routes = tegra210_i2s_routes, + .num_dapm_routes = ARRAY_SIZE(tegra210_i2s_routes), + .controls = tegra210_i2s_controls, + .num_controls = ARRAY_SIZE(tegra210_i2s_controls), + .non_legacy_dai_naming = 1, +}; + +static bool tegra210_i2s_wr_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TEGRA210_I2S_RX_ENABLE ... TEGRA210_I2S_RX_SOFT_RESET: + case TEGRA210_I2S_RX_INT_MASK ... TEGRA210_I2S_RX_CLK_TRIM: + case TEGRA210_I2S_TX_ENABLE ... TEGRA210_I2S_TX_SOFT_RESET: + case TEGRA210_I2S_TX_INT_MASK ... TEGRA210_I2S_TX_CLK_TRIM: + case TEGRA210_I2S_ENABLE ... TEGRA210_I2S_CG: + case TEGRA210_I2S_CTRL ... TEGRA210_I2S_CYA: + return true; + default: + return false; + }; +} + +static bool tegra210_i2s_rd_reg(struct device *dev, unsigned int reg) +{ + if (tegra210_i2s_wr_reg(dev, reg)) + return true; + + switch (reg) { + case TEGRA210_I2S_RX_STATUS: + case TEGRA210_I2S_RX_INT_STATUS: + case TEGRA210_I2S_RX_CIF_FIFO_STATUS: + case TEGRA210_I2S_TX_STATUS: + case TEGRA210_I2S_TX_INT_STATUS: + case TEGRA210_I2S_TX_CIF_FIFO_STATUS: + case TEGRA210_I2S_STATUS: + case TEGRA210_I2S_INT_STATUS: + return true; + default: + return false; + }; +} + +static bool tegra210_i2s_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TEGRA210_I2S_RX_STATUS: + case TEGRA210_I2S_RX_INT_STATUS: + case TEGRA210_I2S_RX_CIF_FIFO_STATUS: + case TEGRA210_I2S_TX_STATUS: + case TEGRA210_I2S_TX_INT_STATUS: + case TEGRA210_I2S_TX_CIF_FIFO_STATUS: + case TEGRA210_I2S_STATUS: + case TEGRA210_I2S_INT_STATUS: + case TEGRA210_I2S_RX_SOFT_RESET: + case TEGRA210_I2S_TX_SOFT_RESET: + return true; + default: + return false; + }; +} + +static const struct regmap_config tegra210_i2s_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = TEGRA210_I2S_CYA, + .writeable_reg = tegra210_i2s_wr_reg, + .readable_reg = tegra210_i2s_rd_reg, + .volatile_reg = tegra210_i2s_volatile_reg, + .reg_defaults = tegra210_i2s_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(tegra210_i2s_reg_defaults), + .cache_type = REGCACHE_FLAT, +}; + +static int tegra210_i2s_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct tegra210_i2s *i2s; + void __iomem *regs; + int err; + + i2s = devm_kzalloc(dev, sizeof(*i2s), GFP_KERNEL); + if (!i2s) + return -ENOMEM; + + i2s->rx_fifo_th = DEFAULT_I2S_RX_FIFO_THRESHOLD; + i2s->tx_mask = DEFAULT_I2S_SLOT_MASK; + i2s->rx_mask = DEFAULT_I2S_SLOT_MASK; + i2s->loopback = false; + + dev_set_drvdata(dev, i2s); + + i2s->clk_i2s = devm_clk_get(dev, "i2s"); + if (IS_ERR(i2s->clk_i2s)) { + dev_err(dev, "can't retrieve I2S bit clock\n"); + return PTR_ERR(i2s->clk_i2s); + } + + /* + * Not an error, as this clock is needed only when some other I/O + * requires input clock from current I2S instance, which is + * configurable from DT. + */ + i2s->clk_sync_input = devm_clk_get(dev, "sync_input"); + if (IS_ERR(i2s->clk_sync_input)) + dev_dbg(dev, "can't retrieve I2S sync input clock\n"); + + regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + i2s->regmap = devm_regmap_init_mmio(dev, regs, + &tegra210_i2s_regmap_config); + if (IS_ERR(i2s->regmap)) { + dev_err(dev, "regmap init failed\n"); + return PTR_ERR(i2s->regmap); + } + + regcache_cache_only(i2s->regmap, true); + + err = devm_snd_soc_register_component(dev, &tegra210_i2s_cmpnt, + tegra210_i2s_dais, + ARRAY_SIZE(tegra210_i2s_dais)); + if (err) { + dev_err(dev, "can't register I2S component, err: %d\n", err); + return err; + } + + pm_runtime_enable(dev); + + return 0; +} + +static int tegra210_i2s_remove(struct platform_device *pdev) +{ + pm_runtime_disable(&pdev->dev); + + return 0; +} + +static const struct dev_pm_ops tegra210_i2s_pm_ops = { + SET_RUNTIME_PM_OPS(tegra210_i2s_runtime_suspend, + tegra210_i2s_runtime_resume, NULL) + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) +}; + +static const struct of_device_id tegra210_i2s_of_match[] = { + { .compatible = "nvidia,tegra210-i2s" }, + {}, +}; +MODULE_DEVICE_TABLE(of, tegra210_i2s_of_match); + +static struct platform_driver tegra210_i2s_driver = { + .driver = { + .name = "tegra210-i2s", + .of_match_table = tegra210_i2s_of_match, + .pm = &tegra210_i2s_pm_ops, + }, + .probe = tegra210_i2s_probe, + .remove = tegra210_i2s_remove, +}; +module_platform_driver(tegra210_i2s_driver) + +MODULE_AUTHOR("Songhee Baek <sbaek@nvidia.com>"); +MODULE_DESCRIPTION("Tegra210 ASoC I2S driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/tegra/tegra210_i2s.h b/sound/soc/tegra/tegra210_i2s.h new file mode 100644 index 000000000..030d70c45 --- /dev/null +++ b/sound/soc/tegra/tegra210_i2s.h @@ -0,0 +1,126 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * tegra210_i2s.h - Definitions for Tegra210 I2S driver + * + * Copyright (c) 2020 NVIDIA CORPORATION. All rights reserved. + * + */ + +#ifndef __TEGRA210_I2S_H__ +#define __TEGRA210_I2S_H__ + +/* Register offsets from I2S*_BASE */ +#define TEGRA210_I2S_RX_ENABLE 0x0 +#define TEGRA210_I2S_RX_SOFT_RESET 0x4 +#define TEGRA210_I2S_RX_STATUS 0x0c +#define TEGRA210_I2S_RX_INT_STATUS 0x10 +#define TEGRA210_I2S_RX_INT_MASK 0x14 +#define TEGRA210_I2S_RX_INT_SET 0x18 +#define TEGRA210_I2S_RX_INT_CLEAR 0x1c +#define TEGRA210_I2S_RX_CIF_CTRL 0x20 +#define TEGRA210_I2S_RX_CTRL 0x24 +#define TEGRA210_I2S_RX_SLOT_CTRL 0x28 +#define TEGRA210_I2S_RX_CLK_TRIM 0x2c +#define TEGRA210_I2S_RX_CYA 0x30 +#define TEGRA210_I2S_RX_CIF_FIFO_STATUS 0x34 +#define TEGRA210_I2S_TX_ENABLE 0x40 +#define TEGRA210_I2S_TX_SOFT_RESET 0x44 +#define TEGRA210_I2S_TX_STATUS 0x4c +#define TEGRA210_I2S_TX_INT_STATUS 0x50 +#define TEGRA210_I2S_TX_INT_MASK 0x54 +#define TEGRA210_I2S_TX_INT_SET 0x58 +#define TEGRA210_I2S_TX_INT_CLEAR 0x5c +#define TEGRA210_I2S_TX_CIF_CTRL 0x60 +#define TEGRA210_I2S_TX_CTRL 0x64 +#define TEGRA210_I2S_TX_SLOT_CTRL 0x68 +#define TEGRA210_I2S_TX_CLK_TRIM 0x6c +#define TEGRA210_I2S_TX_CYA 0x70 +#define TEGRA210_I2S_TX_CIF_FIFO_STATUS 0x74 +#define TEGRA210_I2S_ENABLE 0x80 +#define TEGRA210_I2S_SOFT_RESET 0x84 +#define TEGRA210_I2S_CG 0x88 +#define TEGRA210_I2S_STATUS 0x8c +#define TEGRA210_I2S_INT_STATUS 0x90 +#define TEGRA210_I2S_CTRL 0xa0 +#define TEGRA210_I2S_TIMING 0xa4 +#define TEGRA210_I2S_SLOT_CTRL 0xa8 +#define TEGRA210_I2S_CLK_TRIM 0xac +#define TEGRA210_I2S_CYA 0xb0 + +/* Bit fields, shifts and masks */ +#define I2S_DATA_SHIFT 8 +#define I2S_CTRL_DATA_OFFSET_MASK (0x7ff << I2S_DATA_SHIFT) + +#define I2S_EN_SHIFT 0 +#define I2S_EN_MASK BIT(I2S_EN_SHIFT) +#define I2S_EN BIT(I2S_EN_SHIFT) + +#define I2S_FSYNC_WIDTH_SHIFT 24 +#define I2S_CTRL_FSYNC_WIDTH_MASK (0xff << I2S_FSYNC_WIDTH_SHIFT) + +#define I2S_POS_EDGE 0 +#define I2S_NEG_EDGE 1 +#define I2S_EDGE_SHIFT 20 +#define I2S_CTRL_EDGE_CTRL_MASK BIT(I2S_EDGE_SHIFT) +#define I2S_CTRL_EDGE_CTRL_POS_EDGE (I2S_POS_EDGE << I2S_EDGE_SHIFT) +#define I2S_CTRL_EDGE_CTRL_NEG_EDGE (I2S_NEG_EDGE << I2S_EDGE_SHIFT) + +#define I2S_FMT_LRCK 0 +#define I2S_FMT_FSYNC 1 +#define I2S_FMT_SHIFT 12 +#define I2S_CTRL_FRAME_FMT_MASK (7 << I2S_FMT_SHIFT) +#define I2S_CTRL_FRAME_FMT_LRCK_MODE (I2S_FMT_LRCK << I2S_FMT_SHIFT) +#define I2S_CTRL_FRAME_FMT_FSYNC_MODE (I2S_FMT_FSYNC << I2S_FMT_SHIFT) + +#define I2S_CTRL_MASTER_EN_SHIFT 10 +#define I2S_CTRL_MASTER_EN_MASK BIT(I2S_CTRL_MASTER_EN_SHIFT) +#define I2S_CTRL_MASTER_EN BIT(I2S_CTRL_MASTER_EN_SHIFT) + +#define I2S_CTRL_LRCK_POL_SHIFT 9 +#define I2S_CTRL_LRCK_POL_MASK BIT(I2S_CTRL_LRCK_POL_SHIFT) +#define I2S_CTRL_LRCK_POL_LOW (0 << I2S_CTRL_LRCK_POL_SHIFT) +#define I2S_CTRL_LRCK_POL_HIGH BIT(I2S_CTRL_LRCK_POL_SHIFT) + +#define I2S_CTRL_LPBK_SHIFT 8 +#define I2S_CTRL_LPBK_MASK BIT(I2S_CTRL_LPBK_SHIFT) +#define I2S_CTRL_LPBK_EN BIT(I2S_CTRL_LPBK_SHIFT) + +#define I2S_BITS_8 1 +#define I2S_BITS_16 3 +#define I2S_BITS_32 7 +#define I2S_CTRL_BIT_SIZE_MASK 0x7 + +#define I2S_TIMING_CH_BIT_CNT_MASK 0x7ff +#define I2S_TIMING_CH_BIT_CNT_SHIFT 0 + +#define I2S_SOFT_RESET_SHIFT 0 +#define I2S_SOFT_RESET_MASK BIT(I2S_SOFT_RESET_SHIFT) +#define I2S_SOFT_RESET_EN BIT(I2S_SOFT_RESET_SHIFT) + +#define I2S_RX_FIFO_DEPTH 64 +#define DEFAULT_I2S_RX_FIFO_THRESHOLD 3 + +#define DEFAULT_I2S_SLOT_MASK 0xffff + +enum tegra210_i2s_path { + I2S_RX_PATH, + I2S_TX_PATH, + I2S_PATHS, +}; + +struct tegra210_i2s { + struct clk *clk_i2s; + struct clk *clk_sync_input; + struct regmap *regmap; + unsigned int stereo_to_mono[I2S_PATHS]; + unsigned int mono_to_stereo[I2S_PATHS]; + unsigned int dai_fmt; + unsigned int fsync_width; + unsigned int bclk_ratio; + unsigned int tx_mask; + unsigned int rx_mask; + unsigned int rx_fifo_th; + bool loopback; +}; + +#endif diff --git a/sound/soc/tegra/tegra30_ahub.c b/sound/soc/tegra/tegra30_ahub.c new file mode 100644 index 000000000..156e3b9d6 --- /dev/null +++ b/sound/soc/tegra/tegra30_ahub.c @@ -0,0 +1,743 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * tegra30_ahub.c - Tegra30 AHUB driver + * + * Copyright (c) 2011,2012, NVIDIA CORPORATION. All rights reserved. + */ + +#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/reset.h> +#include <linux/slab.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_unprepare(ahub->clk_apbif); + clk_disable_unprepare(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_prepare_enable(ahub->clk_d_audio); + if (ret) { + dev_err(dev, "clk_enable d_audio failed: %d\n", ret); + return ret; + } + ret = clk_prepare_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, + char *dmachan, int dmachan_len, + dma_addr_t *fiforeg) +{ + int channel; + u32 reg, val; + struct tegra30_ahub_cif_conf cif_conf; + + 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; + snprintf(dmachan, dmachan_len, "rx%d", channel); + *fiforeg = ahub->apbif_addr + TEGRA30_AHUB_CHANNEL_RXFIFO + + (channel * TEGRA30_AHUB_CHANNEL_RXFIFO_STRIDE); + + pm_runtime_get_sync(ahub->dev); + + 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); + + cif_conf.threshold = 0; + cif_conf.audio_channels = 2; + cif_conf.client_channels = 2; + cif_conf.audio_bits = TEGRA30_AUDIOCIF_BITS_16; + cif_conf.client_bits = TEGRA30_AUDIOCIF_BITS_16; + cif_conf.expand = 0; + cif_conf.stereo_conv = 0; + cif_conf.replicate = 0; + cif_conf.direction = TEGRA30_AUDIOCIF_DIRECTION_RX; + cif_conf.truncate = 0; + cif_conf.mono_conv = 0; + + reg = TEGRA30_AHUB_CIF_RX_CTRL + + (channel * TEGRA30_AHUB_CIF_RX_CTRL_STRIDE); + ahub->soc_data->set_audio_cif(ahub->regmap_apbif, reg, &cif_conf); + + pm_runtime_put(ahub->dev); + + 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; + + pm_runtime_get_sync(ahub->dev); + + 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); + + pm_runtime_put(ahub->dev); + + 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; + + pm_runtime_get_sync(ahub->dev); + + 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); + + pm_runtime_put(ahub->dev); + + 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, + char *dmachan, int dmachan_len, + dma_addr_t *fiforeg) +{ + int channel; + u32 reg, val; + struct tegra30_ahub_cif_conf cif_conf; + + 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; + snprintf(dmachan, dmachan_len, "tx%d", channel); + *fiforeg = ahub->apbif_addr + TEGRA30_AHUB_CHANNEL_TXFIFO + + (channel * TEGRA30_AHUB_CHANNEL_TXFIFO_STRIDE); + + pm_runtime_get_sync(ahub->dev); + + 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); + + cif_conf.threshold = 0; + cif_conf.audio_channels = 2; + cif_conf.client_channels = 2; + cif_conf.audio_bits = TEGRA30_AUDIOCIF_BITS_16; + cif_conf.client_bits = TEGRA30_AUDIOCIF_BITS_16; + cif_conf.expand = 0; + cif_conf.stereo_conv = 0; + cif_conf.replicate = 0; + cif_conf.direction = TEGRA30_AUDIOCIF_DIRECTION_TX; + cif_conf.truncate = 0; + cif_conf.mono_conv = 0; + + reg = TEGRA30_AHUB_CIF_TX_CTRL + + (channel * TEGRA30_AHUB_CIF_TX_CTRL_STRIDE); + ahub->soc_data->set_audio_cif(ahub->regmap_apbif, reg, &cif_conf); + + pm_runtime_put(ahub->dev); + + 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; + + pm_runtime_get_sync(ahub->dev); + + 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); + + pm_runtime_put(ahub->dev); + + 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; + + pm_runtime_get_sync(ahub->dev); + + 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); + + pm_runtime_put(ahub->dev); + + 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; + + pm_runtime_get_sync(ahub->dev); + + reg = TEGRA30_AHUB_AUDIO_RX + + (channel * TEGRA30_AHUB_AUDIO_RX_STRIDE); + tegra30_audio_write(reg, 1 << txcif); + + pm_runtime_put(ahub->dev); + + 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; + + pm_runtime_get_sync(ahub->dev); + + reg = TEGRA30_AHUB_AUDIO_RX + + (channel * TEGRA30_AHUB_AUDIO_RX_STRIDE); + tegra30_audio_write(reg, 0); + + pm_runtime_put(ahub->dev); + + return 0; +} +EXPORT_SYMBOL_GPL(tegra30_ahub_unset_rx_cif_source); + +#define MOD_LIST_MASK_TEGRA30 BIT(0) +#define MOD_LIST_MASK_TEGRA114 BIT(1) +#define MOD_LIST_MASK_TEGRA124 BIT(2) + +#define MOD_LIST_MASK_TEGRA30_OR_LATER \ + (MOD_LIST_MASK_TEGRA30 | MOD_LIST_MASK_TEGRA114 | \ + MOD_LIST_MASK_TEGRA124) +#define MOD_LIST_MASK_TEGRA114_OR_LATER \ + (MOD_LIST_MASK_TEGRA114 | MOD_LIST_MASK_TEGRA124) + +static const struct { + const char *rst_name; + u32 mod_list_mask; +} configlink_mods[] = { + { "i2s0", MOD_LIST_MASK_TEGRA30_OR_LATER }, + { "i2s1", MOD_LIST_MASK_TEGRA30_OR_LATER }, + { "i2s2", MOD_LIST_MASK_TEGRA30_OR_LATER }, + { "i2s3", MOD_LIST_MASK_TEGRA30_OR_LATER }, + { "i2s4", MOD_LIST_MASK_TEGRA30_OR_LATER }, + { "dam0", MOD_LIST_MASK_TEGRA30_OR_LATER }, + { "dam1", MOD_LIST_MASK_TEGRA30_OR_LATER }, + { "dam2", MOD_LIST_MASK_TEGRA30_OR_LATER }, + { "spdif", MOD_LIST_MASK_TEGRA30_OR_LATER }, + { "amx", MOD_LIST_MASK_TEGRA114_OR_LATER }, + { "adx", MOD_LIST_MASK_TEGRA114_OR_LATER }, + { "amx1", MOD_LIST_MASK_TEGRA124 }, + { "adx1", MOD_LIST_MASK_TEGRA124 }, + { "afc0", MOD_LIST_MASK_TEGRA124 }, + { "afc1", MOD_LIST_MASK_TEGRA124 }, + { "afc2", MOD_LIST_MASK_TEGRA124 }, + { "afc3", MOD_LIST_MASK_TEGRA124 }, + { "afc4", MOD_LIST_MASK_TEGRA124 }, + { "afc5", MOD_LIST_MASK_TEGRA124 }, +}; + +#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_FLAT, +}; + +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_FLAT, +}; + +static struct tegra30_ahub_soc_data soc_data_tegra30 = { + .mod_list_mask = MOD_LIST_MASK_TEGRA30, + .set_audio_cif = tegra30_ahub_set_cif, +}; + +static struct tegra30_ahub_soc_data soc_data_tegra114 = { + .mod_list_mask = MOD_LIST_MASK_TEGRA114, + .set_audio_cif = tegra30_ahub_set_cif, +}; + +static struct tegra30_ahub_soc_data soc_data_tegra124 = { + .mod_list_mask = MOD_LIST_MASK_TEGRA124, + .set_audio_cif = tegra124_ahub_set_cif, +}; + +static const struct of_device_id tegra30_ahub_of_match[] = { + { .compatible = "nvidia,tegra124-ahub", .data = &soc_data_tegra124 }, + { .compatible = "nvidia,tegra114-ahub", .data = &soc_data_tegra114 }, + { .compatible = "nvidia,tegra30-ahub", .data = &soc_data_tegra30 }, + {}, +}; + +static int tegra30_ahub_probe(struct platform_device *pdev) +{ + const struct of_device_id *match; + const struct tegra30_ahub_soc_data *soc_data; + struct reset_control *rst; + int i; + struct resource *res0; + void __iomem *regs_apbif, *regs_ahub; + int ret = 0; + + if (ahub) + return -ENODEV; + + match = of_match_device(tegra30_ahub_of_match, &pdev->dev); + if (!match) + return -EINVAL; + soc_data = match->data; + + /* + * 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_mods); i++) { + if (!(configlink_mods[i].mod_list_mask & + soc_data->mod_list_mask)) + continue; + + rst = reset_control_get_exclusive(&pdev->dev, + configlink_mods[i].rst_name); + if (IS_ERR(rst)) { + dev_err(&pdev->dev, "Can't get reset %s\n", + configlink_mods[i].rst_name); + ret = PTR_ERR(rst); + return ret; + } + + ret = reset_control_deassert(rst); + reset_control_put(rst); + if (ret) + return ret; + } + + ahub = devm_kzalloc(&pdev->dev, sizeof(struct tegra30_ahub), + GFP_KERNEL); + if (!ahub) + return -ENOMEM; + dev_set_drvdata(&pdev->dev, ahub); + + ahub->soc_data = soc_data; + ahub->dev = &pdev->dev; + + ahub->clk_d_audio = devm_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); + return ret; + } + + ahub->clk_apbif = devm_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); + return ret; + } + + res0 = platform_get_resource(pdev, IORESOURCE_MEM, 0); + regs_apbif = devm_ioremap_resource(&pdev->dev, res0); + if (IS_ERR(regs_apbif)) + return PTR_ERR(regs_apbif); + + ahub->apbif_addr = res0->start; + + 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); + return ret; + } + regcache_cache_only(ahub->regmap_apbif, true); + + regs_ahub = devm_platform_ioremap_resource(pdev, 1); + if (IS_ERR(regs_ahub)) + return PTR_ERR(regs_ahub); + + 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); + return ret; + } + 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, NULL, &pdev->dev); + + return 0; + +err_pm_disable: + pm_runtime_disable(&pdev->dev); + + return ret; +} + +static int 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); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int tegra30_ahub_suspend(struct device *dev) +{ + regcache_mark_dirty(ahub->regmap_ahub); + regcache_mark_dirty(ahub->regmap_apbif); + + return 0; +} + +static int tegra30_ahub_resume(struct device *dev) +{ + int ret; + + ret = pm_runtime_get_sync(dev); + if (ret < 0) { + pm_runtime_put(dev); + return ret; + } + ret = regcache_sync(ahub->regmap_ahub); + ret |= regcache_sync(ahub->regmap_apbif); + pm_runtime_put(dev); + + return ret; +} +#endif + +static const struct dev_pm_ops tegra30_ahub_pm_ops = { + SET_RUNTIME_PM_OPS(tegra30_ahub_runtime_suspend, + tegra30_ahub_runtime_resume, NULL) + SET_SYSTEM_SLEEP_PM_OPS(tegra30_ahub_suspend, tegra30_ahub_resume) +}; + +static struct platform_driver tegra30_ahub_driver = { + .probe = tegra30_ahub_probe, + .remove = tegra30_ahub_remove, + .driver = { + .name = DRV_NAME, + .of_match_table = tegra30_ahub_of_match, + .pm = &tegra30_ahub_pm_ops, + }, +}; +module_platform_driver(tegra30_ahub_driver); + +void tegra30_ahub_set_cif(struct regmap *regmap, unsigned int reg, + struct tegra30_ahub_cif_conf *conf) +{ + unsigned int value; + + value = (conf->threshold << + TEGRA30_AUDIOCIF_CTRL_FIFO_THRESHOLD_SHIFT) | + ((conf->audio_channels - 1) << + TEGRA30_AUDIOCIF_CTRL_AUDIO_CHANNELS_SHIFT) | + ((conf->client_channels - 1) << + TEGRA30_AUDIOCIF_CTRL_CLIENT_CHANNELS_SHIFT) | + (conf->audio_bits << + TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_SHIFT) | + (conf->client_bits << + TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_SHIFT) | + (conf->expand << + TEGRA30_AUDIOCIF_CTRL_EXPAND_SHIFT) | + (conf->stereo_conv << + TEGRA30_AUDIOCIF_CTRL_STEREO_CONV_SHIFT) | + (conf->replicate << + TEGRA30_AUDIOCIF_CTRL_REPLICATE_SHIFT) | + (conf->direction << + TEGRA30_AUDIOCIF_CTRL_DIRECTION_SHIFT) | + (conf->truncate << + TEGRA30_AUDIOCIF_CTRL_TRUNCATE_SHIFT) | + (conf->mono_conv << + TEGRA30_AUDIOCIF_CTRL_MONO_CONV_SHIFT); + + regmap_write(regmap, reg, value); +} +EXPORT_SYMBOL_GPL(tegra30_ahub_set_cif); + +void tegra124_ahub_set_cif(struct regmap *regmap, unsigned int reg, + struct tegra30_ahub_cif_conf *conf) +{ + unsigned int value; + + value = (conf->threshold << + TEGRA124_AUDIOCIF_CTRL_FIFO_THRESHOLD_SHIFT) | + ((conf->audio_channels - 1) << + TEGRA124_AUDIOCIF_CTRL_AUDIO_CHANNELS_SHIFT) | + ((conf->client_channels - 1) << + TEGRA124_AUDIOCIF_CTRL_CLIENT_CHANNELS_SHIFT) | + (conf->audio_bits << + TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_SHIFT) | + (conf->client_bits << + TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_SHIFT) | + (conf->expand << + TEGRA30_AUDIOCIF_CTRL_EXPAND_SHIFT) | + (conf->stereo_conv << + TEGRA30_AUDIOCIF_CTRL_STEREO_CONV_SHIFT) | + (conf->replicate << + TEGRA30_AUDIOCIF_CTRL_REPLICATE_SHIFT) | + (conf->direction << + TEGRA30_AUDIOCIF_CTRL_DIRECTION_SHIFT) | + (conf->truncate << + TEGRA30_AUDIOCIF_CTRL_TRUNCATE_SHIFT) | + (conf->mono_conv << + TEGRA30_AUDIOCIF_CTRL_MONO_CONV_SHIFT); + + regmap_write(regmap, reg, value); +} +EXPORT_SYMBOL_GPL(tegra124_ahub_set_cif); + +MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>"); +MODULE_DESCRIPTION("Tegra30 AHUB driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRV_NAME); +MODULE_DEVICE_TABLE(of, tegra30_ahub_of_match); diff --git a/sound/soc/tegra/tegra30_ahub.h b/sound/soc/tegra/tegra30_ahub.h new file mode 100644 index 000000000..6889c5f23 --- /dev/null +++ b/sound/soc/tegra/tegra30_ahub.h @@ -0,0 +1,523 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * tegra30_ahub.h - Definitions for Tegra30 AHUB driver + * + * Copyright (c) 2011,2012, NVIDIA CORPORATION. All rights reserved. + */ + +#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) + +#define TEGRA124_AUDIOCIF_CTRL_FIFO_THRESHOLD_SHIFT 24 +#define TEGRA124_AUDIOCIF_CTRL_FIFO_THRESHOLD_MASK_US 0x3f +#define TEGRA124_AUDIOCIF_CTRL_FIFO_THRESHOLD_MASK (TEGRA124_AUDIOCIF_CTRL_FIFO_THRESHOLD_MASK_US << TEGRA124_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 TEGRA124_AUDIOCIF_CTRL_AUDIO_CHANNELS_SHIFT 20 +#define TEGRA124_AUDIOCIF_CTRL_AUDIO_CHANNELS_MASK_US 0xf +#define TEGRA124_AUDIOCIF_CTRL_AUDIO_CHANNELS_MASK (TEGRA124_AUDIOCIF_CTRL_AUDIO_CHANNELS_MASK_US << TEGRA124_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) + +/* Channel count minus 1 */ +#define TEGRA124_AUDIOCIF_CTRL_CLIENT_CHANNELS_SHIFT 16 +#define TEGRA124_AUDIOCIF_CTRL_CLIENT_CHANNELS_MASK_US 0xf +#define TEGRA124_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_SHIFT 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, + char *dmachan, int dmachan_len, + dma_addr_t *fiforeg); +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, + char *dmachan, int dmachan_len, + dma_addr_t *fiforeg); +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_cif_conf { + unsigned int threshold; + unsigned int audio_channels; + unsigned int client_channels; + unsigned int audio_bits; + unsigned int client_bits; + unsigned int expand; + unsigned int stereo_conv; + unsigned int replicate; + unsigned int direction; + unsigned int truncate; + unsigned int mono_conv; +}; + +void tegra30_ahub_set_cif(struct regmap *regmap, unsigned int reg, + struct tegra30_ahub_cif_conf *conf); +void tegra124_ahub_set_cif(struct regmap *regmap, unsigned int reg, + struct tegra30_ahub_cif_conf *conf); + +struct tegra30_ahub_soc_data { + u32 mod_list_mask; + void (*set_audio_cif)(struct regmap *regmap, + unsigned int reg, + struct tegra30_ahub_cif_conf *conf); + /* + * FIXME: There are many more differences in HW, such as: + * - More APBIF channels. + * - Extra separate chunks of register address space to represent + * the extra APBIF channels. + * - More units connected to the AHUB, so that tegra30_ahub_[rt]xcif + * need expansion, coupled with there being more defined bits in + * the AHUB routing registers. + * However, the driver doesn't support those new features yet, so we + * don't represent them here yet. + */ +}; + +struct tegra30_ahub { + const struct tegra30_ahub_soc_data *soc_data; + struct device *dev; + struct clk *clk_d_audio; + struct clk *clk_apbif; + 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 000000000..db5a8587b --- /dev/null +++ b/sound/soc/tegra/tegra30_i2s.c @@ -0,0 +1,602 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * 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> + */ + +#include <linux/clk.h> +#include <linux/device.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.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 <sound/dmaengine_pcm.h> + +#include "tegra30_ahub.h" +#include "tegra30_i2s.h" + +#define DRV_NAME "tegra30-i2s" + +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_unprepare(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_prepare_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; +} + +static int tegra30_i2s_set_fmt(struct snd_soc_dai *dai, + unsigned int fmt) +{ + struct tegra30_i2s *i2s = snd_soc_dai_get_drvdata(dai); + unsigned int mask = 0, val = 0; + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + default: + return -EINVAL; + } + + mask |= TEGRA30_I2S_CTRL_MASTER_ENABLE; + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + val |= TEGRA30_I2S_CTRL_MASTER_ENABLE; + break; + case SND_SOC_DAIFMT_CBM_CFM: + break; + default: + return -EINVAL; + } + + mask |= TEGRA30_I2S_CTRL_FRAME_FORMAT_MASK | + TEGRA30_I2S_CTRL_LRCK_MASK; + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_A: + val |= TEGRA30_I2S_CTRL_FRAME_FORMAT_FSYNC; + val |= TEGRA30_I2S_CTRL_LRCK_L_LOW; + break; + case SND_SOC_DAIFMT_DSP_B: + val |= TEGRA30_I2S_CTRL_FRAME_FORMAT_FSYNC; + val |= TEGRA30_I2S_CTRL_LRCK_R_LOW; + break; + case SND_SOC_DAIFMT_I2S: + val |= TEGRA30_I2S_CTRL_FRAME_FORMAT_LRCK; + val |= TEGRA30_I2S_CTRL_LRCK_L_LOW; + break; + case SND_SOC_DAIFMT_RIGHT_J: + val |= TEGRA30_I2S_CTRL_FRAME_FORMAT_LRCK; + val |= TEGRA30_I2S_CTRL_LRCK_L_LOW; + break; + case SND_SOC_DAIFMT_LEFT_J: + val |= TEGRA30_I2S_CTRL_FRAME_FORMAT_LRCK; + val |= TEGRA30_I2S_CTRL_LRCK_L_LOW; + break; + default: + return -EINVAL; + } + + pm_runtime_get_sync(dai->dev); + regmap_update_bits(i2s->regmap, TEGRA30_I2S_CTRL, mask, val); + pm_runtime_put(dai->dev); + + 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 = dai->dev; + struct tegra30_i2s *i2s = snd_soc_dai_get_drvdata(dai); + unsigned int mask, val, reg; + int ret, sample_size, srate, i2sclock, bitcnt; + struct tegra30_ahub_cif_conf cif_conf; + + if (params_channels(params) != 2) + return -EINVAL; + + mask = TEGRA30_I2S_CTRL_BIT_SIZE_MASK; + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + val = TEGRA30_I2S_CTRL_BIT_SIZE_16; + sample_size = 16; + break; + default: + return -EINVAL; + } + + regmap_update_bits(i2s->regmap, TEGRA30_I2S_CTRL, mask, val); + + 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; + + regmap_write(i2s->regmap, TEGRA30_I2S_TIMING, val); + + cif_conf.threshold = 0; + cif_conf.audio_channels = 2; + cif_conf.client_channels = 2; + cif_conf.audio_bits = TEGRA30_AUDIOCIF_BITS_16; + cif_conf.client_bits = TEGRA30_AUDIOCIF_BITS_16; + cif_conf.expand = 0; + cif_conf.stereo_conv = 0; + cif_conf.replicate = 0; + cif_conf.truncate = 0; + cif_conf.mono_conv = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + cif_conf.direction = TEGRA30_AUDIOCIF_DIRECTION_RX; + reg = TEGRA30_I2S_CIF_RX_CTRL; + } else { + cif_conf.direction = TEGRA30_AUDIOCIF_DIRECTION_TX; + reg = TEGRA30_I2S_CIF_TX_CTRL; + } + + i2s->soc_data->set_audio_cif(i2s->regmap, reg, &cif_conf); + + val = (1 << TEGRA30_I2S_OFFSET_RX_DATA_OFFSET_SHIFT) | + (1 << TEGRA30_I2S_OFFSET_TX_DATA_OFFSET_SHIFT); + regmap_write(i2s->regmap, 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); + regmap_update_bits(i2s->regmap, TEGRA30_I2S_CTRL, + TEGRA30_I2S_CTRL_XFER_EN_TX, + TEGRA30_I2S_CTRL_XFER_EN_TX); +} + +static void tegra30_i2s_stop_playback(struct tegra30_i2s *i2s) +{ + tegra30_ahub_disable_tx_fifo(i2s->playback_fifo_cif); + regmap_update_bits(i2s->regmap, TEGRA30_I2S_CTRL, + TEGRA30_I2S_CTRL_XFER_EN_TX, 0); +} + +static void tegra30_i2s_start_capture(struct tegra30_i2s *i2s) +{ + tegra30_ahub_enable_rx_fifo(i2s->capture_fifo_cif); + regmap_update_bits(i2s->regmap, TEGRA30_I2S_CTRL, + TEGRA30_I2S_CTRL_XFER_EN_RX, + TEGRA30_I2S_CTRL_XFER_EN_RX); +} + +static void tegra30_i2s_stop_capture(struct tegra30_i2s *i2s) +{ + regmap_update_bits(i2s->regmap, TEGRA30_I2S_CTRL, + TEGRA30_I2S_CTRL_XFER_EN_RX, 0); + tegra30_ahub_disable_rx_fifo(i2s->capture_fifo_cif); +} + +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_set_tdm(struct snd_soc_dai *dai, + unsigned int tx_mask, unsigned int rx_mask, + int slots, int slot_width) +{ + struct tegra30_i2s *i2s = snd_soc_dai_get_drvdata(dai); + unsigned int mask, val; + + dev_dbg(dai->dev, "%s: txmask=0x%08x rxmask=0x%08x slots=%d width=%d\n", + __func__, tx_mask, rx_mask, slots, slot_width); + + mask = TEGRA30_I2S_SLOT_CTRL_TOTAL_SLOTS_MASK | + TEGRA30_I2S_SLOT_CTRL_RX_SLOT_ENABLES_MASK | + TEGRA30_I2S_SLOT_CTRL_TX_SLOT_ENABLES_MASK; + + val = (tx_mask << TEGRA30_I2S_SLOT_CTRL_TX_SLOT_ENABLES_SHIFT) | + (rx_mask << TEGRA30_I2S_SLOT_CTRL_RX_SLOT_ENABLES_SHIFT) | + ((slots - 1) << TEGRA30_I2S_SLOT_CTRL_TOTAL_SLOTS_SHIFT); + + pm_runtime_get_sync(dai->dev); + regmap_update_bits(i2s->regmap, TEGRA30_I2S_SLOT_CTRL, mask, val); + /* set the fsync width to minimum of 1 clock width */ + regmap_update_bits(i2s->regmap, TEGRA30_I2S_CH_CTRL, + TEGRA30_I2S_CH_CTRL_FSYNC_WIDTH_MASK, 0x0); + pm_runtime_put(dai->dev); + + 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 const struct snd_soc_dai_ops tegra30_i2s_dai_ops = { + .set_fmt = tegra30_i2s_set_fmt, + .hw_params = tegra30_i2s_hw_params, + .trigger = tegra30_i2s_trigger, + .set_tdm_slot = tegra30_i2s_set_tdm, +}; + +static const struct snd_soc_dai_driver tegra30_i2s_dai_template = { + .probe = tegra30_i2s_probe, + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .stream_name = "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 const struct snd_soc_component_driver tegra30_i2s_component = { + .name = DRV_NAME, +}; + +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_FLAT, +}; + +static const struct tegra30_i2s_soc_data tegra30_i2s_config = { + .set_audio_cif = tegra30_ahub_set_cif, +}; + +static const struct tegra30_i2s_soc_data tegra124_i2s_config = { + .set_audio_cif = tegra124_ahub_set_cif, +}; + +static const struct of_device_id tegra30_i2s_of_match[] = { + { .compatible = "nvidia,tegra124-i2s", .data = &tegra124_i2s_config }, + { .compatible = "nvidia,tegra30-i2s", .data = &tegra30_i2s_config }, + {}, +}; + +static int tegra30_i2s_platform_probe(struct platform_device *pdev) +{ + struct tegra30_i2s *i2s; + const struct of_device_id *match; + u32 cif_ids[2]; + void __iomem *regs; + int ret; + + i2s = devm_kzalloc(&pdev->dev, sizeof(struct tegra30_i2s), GFP_KERNEL); + if (!i2s) { + ret = -ENOMEM; + goto err; + } + dev_set_drvdata(&pdev->dev, i2s); + + match = of_match_device(tegra30_i2s_of_match, &pdev->dev); + if (!match) { + dev_err(&pdev->dev, "Error: No device match found\n"); + ret = -ENODEV; + goto err; + } + i2s->soc_data = (struct tegra30_i2s_soc_data *)match->data; + + 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; + } + + regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(regs)) { + ret = PTR_ERR(regs); + 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; + } + + i2s->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + i2s->playback_dma_data.maxburst = 4; + ret = tegra30_ahub_allocate_tx_fifo(&i2s->playback_fifo_cif, + i2s->playback_dma_chan, + sizeof(i2s->playback_dma_chan), + &i2s->playback_dma_data.addr); + if (ret) { + dev_err(&pdev->dev, "Could not alloc TX FIFO: %d\n", ret); + goto err_suspend; + } + ret = tegra30_ahub_set_rx_cif_source(i2s->playback_i2s_cif, + i2s->playback_fifo_cif); + if (ret) { + dev_err(&pdev->dev, "Could not route TX FIFO: %d\n", ret); + goto err_free_tx_fifo; + } + + i2s->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + i2s->capture_dma_data.maxburst = 4; + ret = tegra30_ahub_allocate_rx_fifo(&i2s->capture_fifo_cif, + i2s->capture_dma_chan, + sizeof(i2s->capture_dma_chan), + &i2s->capture_dma_data.addr); + if (ret) { + dev_err(&pdev->dev, "Could not alloc RX FIFO: %d\n", ret); + goto err_unroute_tx_fifo; + } + ret = tegra30_ahub_set_rx_cif_source(i2s->capture_fifo_cif, + i2s->capture_i2s_cif); + if (ret) { + dev_err(&pdev->dev, "Could not route TX FIFO: %d\n", ret); + goto err_free_rx_fifo; + } + + ret = snd_soc_register_component(&pdev->dev, &tegra30_i2s_component, + &i2s->dai, 1); + if (ret) { + dev_err(&pdev->dev, "Could not register DAI: %d\n", ret); + ret = -ENOMEM; + goto err_unroute_rx_fifo; + } + + ret = tegra_pcm_platform_register_with_chan_names(&pdev->dev, + &i2s->dma_config, i2s->playback_dma_chan, + i2s->capture_dma_chan); + if (ret) { + dev_err(&pdev->dev, "Could not register PCM: %d\n", ret); + goto err_unregister_component; + } + + return 0; + +err_unregister_component: + snd_soc_unregister_component(&pdev->dev); +err_unroute_rx_fifo: + tegra30_ahub_unset_rx_cif_source(i2s->capture_fifo_cif); +err_free_rx_fifo: + tegra30_ahub_free_rx_fifo(i2s->capture_fifo_cif); +err_unroute_tx_fifo: + tegra30_ahub_unset_rx_cif_source(i2s->playback_i2s_cif); +err_free_tx_fifo: + tegra30_ahub_free_tx_fifo(i2s->playback_fifo_cif); +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 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_component(&pdev->dev); + + tegra30_ahub_unset_rx_cif_source(i2s->capture_fifo_cif); + tegra30_ahub_free_rx_fifo(i2s->capture_fifo_cif); + + tegra30_ahub_unset_rx_cif_source(i2s->playback_i2s_cif); + tegra30_ahub_free_tx_fifo(i2s->playback_fifo_cif); + + clk_put(i2s->clk_i2s); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int tegra30_i2s_suspend(struct device *dev) +{ + struct tegra30_i2s *i2s = dev_get_drvdata(dev); + + regcache_mark_dirty(i2s->regmap); + + return 0; +} + +static int tegra30_i2s_resume(struct device *dev) +{ + struct tegra30_i2s *i2s = dev_get_drvdata(dev); + int ret; + + ret = pm_runtime_get_sync(dev); + if (ret < 0) { + pm_runtime_put(dev); + return ret; + } + ret = regcache_sync(i2s->regmap); + pm_runtime_put(dev); + + return ret; +} +#endif + +static const struct dev_pm_ops tegra30_i2s_pm_ops = { + SET_RUNTIME_PM_OPS(tegra30_i2s_runtime_suspend, + tegra30_i2s_runtime_resume, NULL) + SET_SYSTEM_SLEEP_PM_OPS(tegra30_i2s_suspend, tegra30_i2s_resume) +}; + +static struct platform_driver tegra30_i2s_driver = { + .driver = { + .name = DRV_NAME, + .of_match_table = tegra30_i2s_of_match, + .pm = &tegra30_i2s_pm_ops, + }, + .probe = tegra30_i2s_platform_probe, + .remove = 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 000000000..0b1f3125a --- /dev/null +++ b/sound/soc/tegra/tegra30_i2s.h @@ -0,0 +1,240 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * tegra30_i2s.h - Definitions for Tegra30 I2S driver + * + * Copyright (c) 2011,2012, NVIDIA CORPORATION. All rights reserved. + */ + +#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 0x7ff +#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_SLOTS_MASK_US << TEGRA30_I2S_SLOT_CTRL_TOTAL_SLOTS_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_soc_data { + void (*set_audio_cif)(struct regmap *regmap, + unsigned int reg, + struct tegra30_ahub_cif_conf *conf); +}; + +struct tegra30_i2s { + const struct tegra30_i2s_soc_data *soc_data; + 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; + char capture_dma_chan[8]; + struct snd_dmaengine_dai_dma_data capture_dma_data; + enum tegra30_ahub_rxcif playback_i2s_cif; + enum tegra30_ahub_txcif playback_fifo_cif; + char playback_dma_chan[8]; + struct snd_dmaengine_dai_dma_data playback_dma_data; + struct regmap *regmap; + struct snd_dmaengine_pcm_config dma_config; +}; + +#endif diff --git a/sound/soc/tegra/tegra_alc5632.c b/sound/soc/tegra/tegra_alc5632.c new file mode 100644 index 000000000..13a956c60 --- /dev/null +++ b/sound/soc/tegra/tegra_alc5632.c @@ -0,0 +1,260 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* +* 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> + */ + +#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/alc5632.h" + +#include "tegra_asoc_utils.h" + +#define DRV_NAME "tegra-alc5632" + +struct tegra_alc5632 { + struct tegra_asoc_utils_data util_data; + int gpio_hp_det; +}; + +static int tegra_alc5632_asoc_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); + struct snd_soc_card *card = rtd->card; + struct tegra_alc5632 *alc5632 = snd_soc_card_get_drvdata(card); + int srate, mclk; + int err; + + srate = params_rate(params); + mclk = 512 * srate; + + err = tegra_asoc_utils_set_rate(&alc5632->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, 0, mclk, + SND_SOC_CLOCK_IN); + if (err < 0) { + dev_err(card->dev, "codec_dai clock not set\n"); + return err; + } + + return 0; +} + +static const struct snd_soc_ops tegra_alc5632_asoc_ops = { + .hw_params = tegra_alc5632_asoc_hw_params, +}; + +static struct snd_soc_jack tegra_alc5632_hs_jack; + +static struct snd_soc_jack_pin tegra_alc5632_hs_jack_pins[] = { + { + .pin = "Headset Mic", + .mask = SND_JACK_MICROPHONE, + }, + { + .pin = "Headset Stereophone", + .mask = SND_JACK_HEADPHONE, + }, +}; + +static struct snd_soc_jack_gpio tegra_alc5632_hp_jack_gpio = { + .name = "Headset detection", + .report = SND_JACK_HEADSET, + .debounce_time = 150, +}; + +static const struct snd_soc_dapm_widget tegra_alc5632_dapm_widgets[] = { + SND_SOC_DAPM_SPK("Int Spk", NULL), + SND_SOC_DAPM_HP("Headset Stereophone", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_MIC("Digital Mic", NULL), +}; + +static const struct snd_kcontrol_new tegra_alc5632_controls[] = { + SOC_DAPM_PIN_SWITCH("Int Spk"), +}; + +static int tegra_alc5632_asoc_init(struct snd_soc_pcm_runtime *rtd) +{ + int ret; + struct tegra_alc5632 *machine = snd_soc_card_get_drvdata(rtd->card); + + ret = snd_soc_card_jack_new(rtd->card, "Headset Jack", + SND_JACK_HEADSET, + &tegra_alc5632_hs_jack, + tegra_alc5632_hs_jack_pins, + ARRAY_SIZE(tegra_alc5632_hs_jack_pins)); + if (ret) + return ret; + + if (gpio_is_valid(machine->gpio_hp_det)) { + tegra_alc5632_hp_jack_gpio.gpio = machine->gpio_hp_det; + snd_soc_jack_add_gpios(&tegra_alc5632_hs_jack, + 1, + &tegra_alc5632_hp_jack_gpio); + } + + snd_soc_dapm_force_enable_pin(&rtd->card->dapm, "MICBIAS1"); + + return 0; +} + +SND_SOC_DAILINK_DEFS(pcm, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "alc5632-hifi")), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +static struct snd_soc_dai_link tegra_alc5632_dai = { + .name = "ALC5632", + .stream_name = "ALC5632 PCM", + .init = tegra_alc5632_asoc_init, + .ops = &tegra_alc5632_asoc_ops, + .dai_fmt = SND_SOC_DAIFMT_I2S + | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBS_CFS, + SND_SOC_DAILINK_REG(pcm), +}; + +static struct snd_soc_card snd_soc_tegra_alc5632 = { + .name = "tegra-alc5632", + .driver_name = "tegra", + .owner = THIS_MODULE, + .dai_link = &tegra_alc5632_dai, + .num_links = 1, + .controls = tegra_alc5632_controls, + .num_controls = ARRAY_SIZE(tegra_alc5632_controls), + .dapm_widgets = tegra_alc5632_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(tegra_alc5632_dapm_widgets), + .fully_routed = true, +}; + +static int tegra_alc5632_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct snd_soc_card *card = &snd_soc_tegra_alc5632; + struct tegra_alc5632 *alc5632; + int ret; + + alc5632 = devm_kzalloc(&pdev->dev, + sizeof(struct tegra_alc5632), GFP_KERNEL); + if (!alc5632) + return -ENOMEM; + + card->dev = &pdev->dev; + snd_soc_card_set_drvdata(card, alc5632); + + alc5632->gpio_hp_det = of_get_named_gpio(np, "nvidia,hp-det-gpios", 0); + if (alc5632->gpio_hp_det == -EPROBE_DEFER) + return -EPROBE_DEFER; + + 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_alc5632_dai.codecs->of_node = of_parse_phandle( + pdev->dev.of_node, "nvidia,audio-codec", 0); + + if (!tegra_alc5632_dai.codecs->of_node) { + dev_err(&pdev->dev, + "Property 'nvidia,audio-codec' missing or invalid\n"); + ret = -EINVAL; + goto err; + } + + tegra_alc5632_dai.cpus->of_node = of_parse_phandle(np, + "nvidia,i2s-controller", 0); + if (!tegra_alc5632_dai.cpus->of_node) { + dev_err(&pdev->dev, + "Property 'nvidia,i2s-controller' missing or invalid\n"); + ret = -EINVAL; + goto err_put_codec_of_node; + } + + tegra_alc5632_dai.platforms->of_node = tegra_alc5632_dai.cpus->of_node; + + ret = tegra_asoc_utils_init(&alc5632->util_data, &pdev->dev); + if (ret) + goto err_put_cpu_of_node; + + ret = snd_soc_register_card(card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", + ret); + goto err_put_cpu_of_node; + } + + return 0; + +err_put_cpu_of_node: + of_node_put(tegra_alc5632_dai.cpus->of_node); + tegra_alc5632_dai.cpus->of_node = NULL; + tegra_alc5632_dai.platforms->of_node = NULL; +err_put_codec_of_node: + of_node_put(tegra_alc5632_dai.codecs->of_node); + tegra_alc5632_dai.codecs->of_node = NULL; +err: + return ret; +} + +static int tegra_alc5632_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + + snd_soc_unregister_card(card); + + of_node_put(tegra_alc5632_dai.cpus->of_node); + tegra_alc5632_dai.cpus->of_node = NULL; + tegra_alc5632_dai.platforms->of_node = NULL; + of_node_put(tegra_alc5632_dai.codecs->of_node); + tegra_alc5632_dai.codecs->of_node = NULL; + + return 0; +} + +static const struct of_device_id tegra_alc5632_of_match[] = { + { .compatible = "nvidia,tegra-audio-alc5632", }, + {}, +}; + +static struct platform_driver tegra_alc5632_driver = { + .driver = { + .name = DRV_NAME, + .pm = &snd_soc_pm_ops, + .of_match_table = tegra_alc5632_of_match, + }, + .probe = tegra_alc5632_probe, + .remove = tegra_alc5632_remove, +}; +module_platform_driver(tegra_alc5632_driver); + +MODULE_AUTHOR("Leon Romanovsky <leon@leon.nu>"); +MODULE_DESCRIPTION("Tegra+ALC5632 machine ASoC driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRV_NAME); +MODULE_DEVICE_TABLE(of, tegra_alc5632_of_match); diff --git a/sound/soc/tegra/tegra_asoc_utils.c b/sound/soc/tegra/tegra_asoc_utils.c new file mode 100644 index 000000000..587f62a28 --- /dev/null +++ b/sound/soc/tegra/tegra_asoc_utils.c @@ -0,0 +1,225 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * tegra_asoc_utils.c - Harmony machine ASoC driver + * + * Author: Stephen Warren <swarren@nvidia.com> + * Copyright (C) 2010,2012 - NVIDIA, Inc. + */ + +#include <linux/clk.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> + +#include "tegra_asoc_utils.h" + +int tegra_asoc_utils_set_rate(struct tegra_asoc_utils_data *data, int srate, + int mclk) +{ + int new_baseclock; + bool clk_change; + int err; + + switch (srate) { + case 11025: + case 22050: + case 44100: + case 88200: + if (data->soc == TEGRA_ASOC_UTILS_SOC_TEGRA20) + new_baseclock = 56448000; + else if (data->soc == TEGRA_ASOC_UTILS_SOC_TEGRA30) + new_baseclock = 564480000; + else + new_baseclock = 282240000; + break; + case 8000: + case 16000: + case 32000: + case 48000: + case 64000: + case 96000: + if (data->soc == TEGRA_ASOC_UTILS_SOC_TEGRA20) + new_baseclock = 73728000; + else if (data->soc == TEGRA_ASOC_UTILS_SOC_TEGRA30) + new_baseclock = 552960000; + else + new_baseclock = 368640000; + break; + default: + return -EINVAL; + } + + clk_change = ((new_baseclock != data->set_baseclock) || + (mclk != data->set_mclk)); + if (!clk_change) + return 0; + + data->set_baseclock = 0; + data->set_mclk = 0; + + clk_disable_unprepare(data->clk_cdev1); + + err = clk_set_rate(data->clk_pll_a, new_baseclock); + if (err) { + dev_err(data->dev, "Can't set pll_a rate: %d\n", err); + return err; + } + + err = clk_set_rate(data->clk_pll_a_out0, mclk); + if (err) { + dev_err(data->dev, "Can't set pll_a_out0 rate: %d\n", err); + return err; + } + + /* Don't set cdev1/extern1 rate; it's locked to pll_a_out0 */ + + err = clk_prepare_enable(data->clk_cdev1); + if (err) { + dev_err(data->dev, "Can't enable cdev1: %d\n", err); + return err; + } + + data->set_baseclock = new_baseclock; + data->set_mclk = mclk; + + return 0; +} +EXPORT_SYMBOL_GPL(tegra_asoc_utils_set_rate); + +int tegra_asoc_utils_set_ac97_rate(struct tegra_asoc_utils_data *data) +{ + const int pll_rate = 73728000; + const int ac97_rate = 24576000; + int err; + + clk_disable_unprepare(data->clk_cdev1); + + /* + * AC97 rate is fixed at 24.576MHz and is used for both the host + * controller and the external codec + */ + err = clk_set_rate(data->clk_pll_a, pll_rate); + if (err) { + dev_err(data->dev, "Can't set pll_a rate: %d\n", err); + return err; + } + + err = clk_set_rate(data->clk_pll_a_out0, ac97_rate); + if (err) { + dev_err(data->dev, "Can't set pll_a_out0 rate: %d\n", err); + return err; + } + + /* Don't set cdev1/extern1 rate; it's locked to pll_a_out0 */ + + err = clk_prepare_enable(data->clk_cdev1); + if (err) { + dev_err(data->dev, "Can't enable cdev1: %d\n", err); + return err; + } + + data->set_baseclock = pll_rate; + data->set_mclk = ac97_rate; + + return 0; +} +EXPORT_SYMBOL_GPL(tegra_asoc_utils_set_ac97_rate); + +int tegra_asoc_utils_init(struct tegra_asoc_utils_data *data, + struct device *dev) +{ + struct clk *clk_out_1, *clk_extern1; + int ret; + + 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 (of_machine_is_compatible("nvidia,tegra114")) + data->soc = TEGRA_ASOC_UTILS_SOC_TEGRA114; + else if (of_machine_is_compatible("nvidia,tegra124")) + data->soc = TEGRA_ASOC_UTILS_SOC_TEGRA124; + else { + dev_err(data->dev, "SoC unknown to Tegra ASoC utils\n"); + return -EINVAL; + } + + data->clk_pll_a = devm_clk_get(dev, "pll_a"); + if (IS_ERR(data->clk_pll_a)) { + dev_err(data->dev, "Can't retrieve clk pll_a\n"); + return PTR_ERR(data->clk_pll_a); + } + + data->clk_pll_a_out0 = devm_clk_get(dev, "pll_a_out0"); + if (IS_ERR(data->clk_pll_a_out0)) { + dev_err(data->dev, "Can't retrieve clk pll_a_out0\n"); + return PTR_ERR(data->clk_pll_a_out0); + } + + data->clk_cdev1 = devm_clk_get(dev, "mclk"); + if (IS_ERR(data->clk_cdev1)) { + dev_err(data->dev, "Can't retrieve clk cdev1\n"); + return PTR_ERR(data->clk_cdev1); + } + + /* + * If clock parents are not set in DT, configure here to use clk_out_1 + * as mclk and extern1 as parent for Tegra30 and higher. + */ + if (!of_find_property(dev->of_node, "assigned-clock-parents", NULL) && + data->soc > TEGRA_ASOC_UTILS_SOC_TEGRA20) { + dev_warn(data->dev, + "Configuring clocks for a legacy device-tree\n"); + dev_warn(data->dev, + "Please update DT to use assigned-clock-parents\n"); + clk_extern1 = devm_clk_get(dev, "extern1"); + if (IS_ERR(clk_extern1)) { + dev_err(data->dev, "Can't retrieve clk extern1\n"); + return PTR_ERR(clk_extern1); + } + + ret = clk_set_parent(clk_extern1, data->clk_pll_a_out0); + if (ret < 0) { + dev_err(data->dev, + "Set parent failed for clk extern1\n"); + return ret; + } + + clk_out_1 = devm_clk_get(dev, "pmc_clk_out_1"); + if (IS_ERR(clk_out_1)) { + dev_err(data->dev, "Can't retrieve pmc_clk_out_1\n"); + return PTR_ERR(clk_out_1); + } + + ret = clk_set_parent(clk_out_1, clk_extern1); + if (ret < 0) { + dev_err(data->dev, + "Set parent failed for pmc_clk_out_1\n"); + return ret; + } + + data->clk_cdev1 = clk_out_1; + } + + /* + * FIXME: There is some unknown dependency between audio mclk disable + * and suspend-resume functionality on Tegra30, although audio mclk is + * only needed for audio. + */ + ret = clk_prepare_enable(data->clk_cdev1); + if (ret) { + dev_err(data->dev, "Can't enable cdev1: %d\n", ret); + return ret; + } + + return 0; +} +EXPORT_SYMBOL_GPL(tegra_asoc_utils_init); + +MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>"); +MODULE_DESCRIPTION("Tegra ASoC utility code"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/tegra/tegra_asoc_utils.h b/sound/soc/tegra/tegra_asoc_utils.h new file mode 100644 index 000000000..a34439587 --- /dev/null +++ b/sound/soc/tegra/tegra_asoc_utils.h @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * tegra_asoc_utils.h - Definitions for Tegra DAS driver + * + * Author: Stephen Warren <swarren@nvidia.com> + * Copyright (C) 2010,2012 - NVIDIA, Inc. + */ + +#ifndef __TEGRA_ASOC_UTILS_H__ +#define __TEGRA_ASOC_UTILS_H__ + +struct clk; +struct device; + +enum tegra_asoc_utils_soc { + TEGRA_ASOC_UTILS_SOC_TEGRA20, + TEGRA_ASOC_UTILS_SOC_TEGRA30, + TEGRA_ASOC_UTILS_SOC_TEGRA114, + TEGRA_ASOC_UTILS_SOC_TEGRA124, +}; + +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; + int set_baseclock; + int set_mclk; +}; + +int tegra_asoc_utils_set_rate(struct tegra_asoc_utils_data *data, int srate, + int mclk); +int tegra_asoc_utils_set_ac97_rate(struct tegra_asoc_utils_data *data); +int tegra_asoc_utils_init(struct tegra_asoc_utils_data *data, + struct device *dev); + +#endif diff --git a/sound/soc/tegra/tegra_cif.h b/sound/soc/tegra/tegra_cif.h new file mode 100644 index 000000000..7cca8068f --- /dev/null +++ b/sound/soc/tegra/tegra_cif.h @@ -0,0 +1,65 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * tegra_cif.h - TEGRA Audio CIF Programming + * + * Copyright (c) 2020 NVIDIA CORPORATION. All rights reserved. + * + */ + +#ifndef __TEGRA_CIF_H__ +#define __TEGRA_CIF_H__ + +#include <linux/regmap.h> + +#define TEGRA_ACIF_CTRL_FIFO_TH_SHIFT 24 +#define TEGRA_ACIF_CTRL_AUDIO_CH_SHIFT 20 +#define TEGRA_ACIF_CTRL_CLIENT_CH_SHIFT 16 +#define TEGRA_ACIF_CTRL_AUDIO_BITS_SHIFT 12 +#define TEGRA_ACIF_CTRL_CLIENT_BITS_SHIFT 8 +#define TEGRA_ACIF_CTRL_EXPAND_SHIFT 6 +#define TEGRA_ACIF_CTRL_STEREO_CONV_SHIFT 4 +#define TEGRA_ACIF_CTRL_REPLICATE_SHIFT 3 +#define TEGRA_ACIF_CTRL_TRUNCATE_SHIFT 1 +#define TEGRA_ACIF_CTRL_MONO_CONV_SHIFT 0 + +/* AUDIO/CLIENT_BITS values */ +#define TEGRA_ACIF_BITS_8 1 +#define TEGRA_ACIF_BITS_16 3 +#define TEGRA_ACIF_BITS_24 5 +#define TEGRA_ACIF_BITS_32 7 + +#define TEGRA_ACIF_UPDATE_MASK 0x3ffffffb + +struct tegra_cif_conf { + unsigned int threshold; + unsigned int audio_ch; + unsigned int client_ch; + unsigned int audio_bits; + unsigned int client_bits; + unsigned int expand; + unsigned int stereo_conv; + unsigned int replicate; + unsigned int truncate; + unsigned int mono_conv; +}; + +static inline void tegra_set_cif(struct regmap *regmap, unsigned int reg, + struct tegra_cif_conf *conf) +{ + unsigned int value; + + value = (conf->threshold << TEGRA_ACIF_CTRL_FIFO_TH_SHIFT) | + ((conf->audio_ch - 1) << TEGRA_ACIF_CTRL_AUDIO_CH_SHIFT) | + ((conf->client_ch - 1) << TEGRA_ACIF_CTRL_CLIENT_CH_SHIFT) | + (conf->audio_bits << TEGRA_ACIF_CTRL_AUDIO_BITS_SHIFT) | + (conf->client_bits << TEGRA_ACIF_CTRL_CLIENT_BITS_SHIFT) | + (conf->expand << TEGRA_ACIF_CTRL_EXPAND_SHIFT) | + (conf->stereo_conv << TEGRA_ACIF_CTRL_STEREO_CONV_SHIFT) | + (conf->replicate << TEGRA_ACIF_CTRL_REPLICATE_SHIFT) | + (conf->truncate << TEGRA_ACIF_CTRL_TRUNCATE_SHIFT) | + (conf->mono_conv << TEGRA_ACIF_CTRL_MONO_CONV_SHIFT); + + regmap_update_bits(regmap, reg, TEGRA_ACIF_UPDATE_MASK, value); +} + +#endif diff --git a/sound/soc/tegra/tegra_max98090.c b/sound/soc/tegra/tegra_max98090.c new file mode 100644 index 000000000..2fdf46bad --- /dev/null +++ b/sound/soc/tegra/tegra_max98090.c @@ -0,0 +1,279 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Tegra machine ASoC driver for boards using a MAX90809 CODEC. + * + * Copyright (c) 2013, NVIDIA CORPORATION. All rights reserved. + * + * Based on code copyright/by: + * + * Copyright (C) 2010-2012 - NVIDIA, Inc. + * Copyright (C) 2011 The AC100 Kernel Team <ac100@lists.lauchpad.net> + * (c) 2009, 2010 Nvidia Graphics Pvt. Ltd. + * Copyright 2007 Wolfson Microelectronics PLC. + */ + +#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 "tegra_asoc_utils.h" + +#define DRV_NAME "tegra-snd-max98090" + +struct tegra_max98090 { + struct tegra_asoc_utils_data util_data; + int gpio_hp_det; + int gpio_mic_det; +}; + +static int tegra_max98090_asoc_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); + struct snd_soc_card *card = rtd->card; + struct tegra_max98090 *machine = snd_soc_card_get_drvdata(card); + int srate, mclk; + int err; + + srate = params_rate(params); + switch (srate) { + case 8000: + case 16000: + case 24000: + case 32000: + case 48000: + case 64000: + case 96000: + mclk = 12288000; + break; + case 11025: + case 22050: + case 44100: + case 88200: + mclk = 11289600; + break; + default: + mclk = 12000000; + 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, 0, mclk, + SND_SOC_CLOCK_IN); + if (err < 0) { + dev_err(card->dev, "codec_dai clock not set\n"); + return err; + } + + return 0; +} + +static const struct snd_soc_ops tegra_max98090_ops = { + .hw_params = tegra_max98090_asoc_hw_params, +}; + +static struct snd_soc_jack tegra_max98090_hp_jack; + +static struct snd_soc_jack_pin tegra_max98090_hp_jack_pins[] = { + { + .pin = "Headphones", + .mask = SND_JACK_HEADPHONE, + }, +}; + +static struct snd_soc_jack_gpio tegra_max98090_hp_jack_gpio = { + .name = "Headphone detection", + .report = SND_JACK_HEADPHONE, + .debounce_time = 150, + .invert = 1, +}; + +static struct snd_soc_jack tegra_max98090_mic_jack; + +static struct snd_soc_jack_pin tegra_max98090_mic_jack_pins[] = { + { + .pin = "Mic Jack", + .mask = SND_JACK_MICROPHONE, + }, +}; + +static struct snd_soc_jack_gpio tegra_max98090_mic_jack_gpio = { + .name = "Mic detection", + .report = SND_JACK_MICROPHONE, + .debounce_time = 150, + .invert = 1, +}; + +static const struct snd_soc_dapm_widget tegra_max98090_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphones", NULL), + SND_SOC_DAPM_SPK("Speakers", NULL), + SND_SOC_DAPM_MIC("Mic Jack", NULL), + SND_SOC_DAPM_MIC("Int Mic", NULL), +}; + +static const struct snd_kcontrol_new tegra_max98090_controls[] = { + SOC_DAPM_PIN_SWITCH("Headphones"), + SOC_DAPM_PIN_SWITCH("Speakers"), + SOC_DAPM_PIN_SWITCH("Mic Jack"), + SOC_DAPM_PIN_SWITCH("Int Mic"), +}; + +static int tegra_max98090_asoc_init(struct snd_soc_pcm_runtime *rtd) +{ + struct tegra_max98090 *machine = snd_soc_card_get_drvdata(rtd->card); + + if (gpio_is_valid(machine->gpio_hp_det)) { + snd_soc_card_jack_new(rtd->card, "Headphones", + SND_JACK_HEADPHONE, + &tegra_max98090_hp_jack, + tegra_max98090_hp_jack_pins, + ARRAY_SIZE(tegra_max98090_hp_jack_pins)); + + tegra_max98090_hp_jack_gpio.gpio = machine->gpio_hp_det; + snd_soc_jack_add_gpios(&tegra_max98090_hp_jack, + 1, + &tegra_max98090_hp_jack_gpio); + } + + if (gpio_is_valid(machine->gpio_mic_det)) { + snd_soc_card_jack_new(rtd->card, "Mic Jack", + SND_JACK_MICROPHONE, + &tegra_max98090_mic_jack, + tegra_max98090_mic_jack_pins, + ARRAY_SIZE(tegra_max98090_mic_jack_pins)); + + tegra_max98090_mic_jack_gpio.gpio = machine->gpio_mic_det; + snd_soc_jack_add_gpios(&tegra_max98090_mic_jack, + 1, + &tegra_max98090_mic_jack_gpio); + } + + return 0; +} + +SND_SOC_DAILINK_DEFS(pcm, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "HiFi")), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +static struct snd_soc_dai_link tegra_max98090_dai = { + .name = "max98090", + .stream_name = "max98090 PCM", + .init = tegra_max98090_asoc_init, + .ops = &tegra_max98090_ops, + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + SND_SOC_DAILINK_REG(pcm), +}; + +static struct snd_soc_card snd_soc_tegra_max98090 = { + .name = "tegra-max98090", + .driver_name = "tegra", + .owner = THIS_MODULE, + .dai_link = &tegra_max98090_dai, + .num_links = 1, + .controls = tegra_max98090_controls, + .num_controls = ARRAY_SIZE(tegra_max98090_controls), + .dapm_widgets = tegra_max98090_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(tegra_max98090_dapm_widgets), + .fully_routed = true, +}; + +static int tegra_max98090_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct snd_soc_card *card = &snd_soc_tegra_max98090; + struct tegra_max98090 *machine; + int ret; + + machine = devm_kzalloc(&pdev->dev, + sizeof(struct tegra_max98090), GFP_KERNEL); + if (!machine) + return -ENOMEM; + + card->dev = &pdev->dev; + snd_soc_card_set_drvdata(card, machine); + + machine->gpio_hp_det = of_get_named_gpio(np, "nvidia,hp-det-gpios", 0); + if (machine->gpio_hp_det == -EPROBE_DEFER) + return -EPROBE_DEFER; + + machine->gpio_mic_det = + of_get_named_gpio(np, "nvidia,mic-det-gpios", 0); + if (machine->gpio_mic_det == -EPROBE_DEFER) + return -EPROBE_DEFER; + + ret = snd_soc_of_parse_card_name(card, "nvidia,model"); + if (ret) + return ret; + + ret = snd_soc_of_parse_audio_routing(card, "nvidia,audio-routing"); + if (ret) + return ret; + + tegra_max98090_dai.codecs->of_node = of_parse_phandle(np, + "nvidia,audio-codec", 0); + if (!tegra_max98090_dai.codecs->of_node) { + dev_err(&pdev->dev, + "Property 'nvidia,audio-codec' missing or invalid\n"); + return -EINVAL; + } + + tegra_max98090_dai.cpus->of_node = of_parse_phandle(np, + "nvidia,i2s-controller", 0); + if (!tegra_max98090_dai.cpus->of_node) { + dev_err(&pdev->dev, + "Property 'nvidia,i2s-controller' missing or invalid\n"); + return -EINVAL; + } + + tegra_max98090_dai.platforms->of_node = tegra_max98090_dai.cpus->of_node; + + ret = tegra_asoc_utils_init(&machine->util_data, &pdev->dev); + if (ret) + return ret; + + ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", + ret); + return ret; + } + + return 0; +} + +static const struct of_device_id tegra_max98090_of_match[] = { + { .compatible = "nvidia,tegra-audio-max98090", }, + {}, +}; + +static struct platform_driver tegra_max98090_driver = { + .driver = { + .name = DRV_NAME, + .pm = &snd_soc_pm_ops, + .of_match_table = tegra_max98090_of_match, + }, + .probe = tegra_max98090_probe, +}; +module_platform_driver(tegra_max98090_driver); + +MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>"); +MODULE_DESCRIPTION("Tegra max98090 machine ASoC driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRV_NAME); +MODULE_DEVICE_TABLE(of, tegra_max98090_of_match); diff --git a/sound/soc/tegra/tegra_pcm.c b/sound/soc/tegra/tegra_pcm.c new file mode 100644 index 000000000..b3f36515c --- /dev/null +++ b/sound/soc/tegra/tegra_pcm.c @@ -0,0 +1,305 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * tegra_pcm.c - Tegra PCM 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> + * Vijay Mali <vmali@nvidia.com> + * + * Copyright (C) 2010 Google, Inc. + * Iliyan Malchev <malchev@google.com> + */ + +#include <linux/module.h> +#include <linux/dma-mapping.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/dmaengine_pcm.h> +#include "tegra_pcm.h" + +static const struct snd_pcm_hardware tegra_pcm_hardware = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED, + .period_bytes_min = 1024, + .period_bytes_max = PAGE_SIZE, + .periods_min = 2, + .periods_max = 8, + .buffer_bytes_max = PAGE_SIZE * 8, + .fifo_size = 4, +}; + +static const struct snd_dmaengine_pcm_config tegra_dmaengine_pcm_config = { + .pcm_hardware = &tegra_pcm_hardware, + .prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config, + .prealloc_buffer_size = PAGE_SIZE * 8, +}; + +int tegra_pcm_platform_register(struct device *dev) +{ + return snd_dmaengine_pcm_register(dev, &tegra_dmaengine_pcm_config, 0); +} +EXPORT_SYMBOL_GPL(tegra_pcm_platform_register); + +int tegra_pcm_platform_register_with_chan_names(struct device *dev, + struct snd_dmaengine_pcm_config *config, + char *txdmachan, char *rxdmachan) +{ + *config = tegra_dmaengine_pcm_config; + config->dma_dev = dev->parent; + config->chan_names[0] = txdmachan; + config->chan_names[1] = rxdmachan; + + return snd_dmaengine_pcm_register(dev, config, 0); +} +EXPORT_SYMBOL_GPL(tegra_pcm_platform_register_with_chan_names); + +void tegra_pcm_platform_unregister(struct device *dev) +{ + return snd_dmaengine_pcm_unregister(dev); +} +EXPORT_SYMBOL_GPL(tegra_pcm_platform_unregister); + +int tegra_pcm_open(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_dmaengine_dai_dma_data *dmap; + struct dma_chan *chan; + struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); + int ret; + + if (rtd->dai_link->no_pcm) + return 0; + + dmap = snd_soc_dai_get_dma_data(cpu_dai, substream); + + /* Set HW params now that initialization is complete */ + snd_soc_set_runtime_hwparams(substream, &tegra_pcm_hardware); + + /* Ensure period size is multiple of 8 */ + ret = snd_pcm_hw_constraint_step(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 0x8); + if (ret) { + dev_err(rtd->dev, "failed to set constraint %d\n", ret); + return ret; + } + + chan = dma_request_slave_channel(cpu_dai->dev, dmap->chan_name); + if (!chan) { + dev_err(cpu_dai->dev, + "dmaengine request slave channel failed! (%s)\n", + dmap->chan_name); + return -ENODEV; + } + + ret = snd_dmaengine_pcm_open(substream, chan); + if (ret) { + dev_err(rtd->dev, + "dmaengine pcm open failed with err %d (%s)\n", ret, + dmap->chan_name); + + dma_release_channel(chan); + + return ret; + } + + return 0; +} +EXPORT_SYMBOL_GPL(tegra_pcm_open); + +int tegra_pcm_close(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + + if (rtd->dai_link->no_pcm) + return 0; + + snd_dmaengine_pcm_close_release_chan(substream); + + return 0; +} +EXPORT_SYMBOL_GPL(tegra_pcm_close); + +int tegra_pcm_hw_params(struct snd_soc_component *component, + struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_dmaengine_dai_dma_data *dmap; + struct dma_slave_config slave_config; + struct dma_chan *chan; + int ret; + + if (rtd->dai_link->no_pcm) + return 0; + + dmap = snd_soc_dai_get_dma_data(asoc_rtd_to_cpu(rtd, 0), substream); + if (!dmap) + return 0; + + chan = snd_dmaengine_pcm_get_chan(substream); + + ret = snd_hwparams_to_dma_slave_config(substream, params, + &slave_config); + if (ret) { + dev_err(rtd->dev, "hw params config failed with err %d\n", ret); + return ret; + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + slave_config.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + slave_config.dst_addr = dmap->addr; + slave_config.dst_maxburst = 8; + } else { + slave_config.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + slave_config.src_addr = dmap->addr; + slave_config.src_maxburst = 8; + } + + ret = dmaengine_slave_config(chan, &slave_config); + if (ret < 0) { + dev_err(rtd->dev, "dma slave config failed with err %d\n", ret); + return ret; + } + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + + return 0; +} +EXPORT_SYMBOL_GPL(tegra_pcm_hw_params); + +int tegra_pcm_hw_free(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + + if (rtd->dai_link->no_pcm) + return 0; + + snd_pcm_set_runtime_buffer(substream, NULL); + + return 0; +} +EXPORT_SYMBOL_GPL(tegra_pcm_hw_free); + +int tegra_pcm_mmap(struct snd_soc_component *component, + struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + + if (rtd->dai_link->no_pcm) + return 0; + + return dma_mmap_wc(substream->pcm->card->dev, vma, runtime->dma_area, + runtime->dma_addr, runtime->dma_bytes); +} +EXPORT_SYMBOL_GPL(tegra_pcm_mmap); + +snd_pcm_uframes_t tegra_pcm_pointer(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + return snd_dmaengine_pcm_pointer(substream); +} +EXPORT_SYMBOL_GPL(tegra_pcm_pointer); + +static int tegra_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream, + size_t size) +{ + struct snd_pcm_substream *substream = pcm->streams[stream].substream; + struct snd_dma_buffer *buf = &substream->dma_buffer; + + buf->area = dma_alloc_wc(pcm->card->dev, size, &buf->addr, GFP_KERNEL); + if (!buf->area) + return -ENOMEM; + + buf->private_data = NULL; + buf->dev.type = SNDRV_DMA_TYPE_DEV; + buf->dev.dev = pcm->card->dev; + buf->bytes = size; + + return 0; +} + +static void tegra_pcm_deallocate_dma_buffer(struct snd_pcm *pcm, int stream) +{ + struct snd_pcm_substream *substream; + struct snd_dma_buffer *buf; + + substream = pcm->streams[stream].substream; + if (!substream) + return; + + buf = &substream->dma_buffer; + if (!buf->area) + return; + + dma_free_wc(pcm->card->dev, buf->bytes, buf->area, buf->addr); + buf->area = NULL; +} + +static int tegra_pcm_dma_allocate(struct snd_soc_pcm_runtime *rtd, + size_t size) +{ + struct snd_card *card = rtd->card->snd_card; + struct snd_pcm *pcm = rtd->pcm; + int ret; + + ret = dma_set_mask(card->dev, DMA_BIT_MASK(32)); + if (ret < 0) + return ret; + + ret = dma_set_coherent_mask(card->dev, DMA_BIT_MASK(32)); + if (ret < 0) + return ret; + + if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) { + ret = tegra_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_PLAYBACK, size); + if (ret) + goto err; + } + + if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) { + ret = tegra_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_CAPTURE, size); + if (ret) + goto err_free_play; + } + + return 0; + +err_free_play: + tegra_pcm_deallocate_dma_buffer(pcm, SNDRV_PCM_STREAM_PLAYBACK); +err: + return ret; +} + +int tegra_pcm_construct(struct snd_soc_component *component, + struct snd_soc_pcm_runtime *rtd) +{ + return tegra_pcm_dma_allocate(rtd, tegra_pcm_hardware.buffer_bytes_max); +} +EXPORT_SYMBOL_GPL(tegra_pcm_construct); + +void tegra_pcm_destruct(struct snd_soc_component *component, + struct snd_pcm *pcm) +{ + tegra_pcm_deallocate_dma_buffer(pcm, SNDRV_PCM_STREAM_CAPTURE); + tegra_pcm_deallocate_dma_buffer(pcm, SNDRV_PCM_STREAM_PLAYBACK); +} +EXPORT_SYMBOL_GPL(tegra_pcm_destruct); + +MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>"); +MODULE_DESCRIPTION("Tegra PCM ASoC driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/tegra/tegra_pcm.h b/sound/soc/tegra/tegra_pcm.h new file mode 100644 index 000000000..4838cdcee --- /dev/null +++ b/sound/soc/tegra/tegra_pcm.h @@ -0,0 +1,47 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * tegra_pcm.h - Definitions for Tegra PCM 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> + */ + +#ifndef __TEGRA_PCM_H__ +#define __TEGRA_PCM_H__ + +#include <sound/dmaengine_pcm.h> +#include <sound/asound.h> + +int tegra_pcm_construct(struct snd_soc_component *component, + struct snd_soc_pcm_runtime *rtd); +void tegra_pcm_destruct(struct snd_soc_component *component, + struct snd_pcm *pcm); +int tegra_pcm_open(struct snd_soc_component *component, + struct snd_pcm_substream *substream); +int tegra_pcm_close(struct snd_soc_component *component, + struct snd_pcm_substream *substream); +int tegra_pcm_hw_params(struct snd_soc_component *component, + struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params); +int tegra_pcm_hw_free(struct snd_soc_component *component, + struct snd_pcm_substream *substream); +int tegra_pcm_mmap(struct snd_soc_component *component, + struct snd_pcm_substream *substream, + struct vm_area_struct *vma); +snd_pcm_uframes_t tegra_pcm_pointer(struct snd_soc_component *component, + struct snd_pcm_substream *substream); +int tegra_pcm_platform_register(struct device *dev); +int tegra_pcm_platform_register_with_chan_names(struct device *dev, + struct snd_dmaengine_pcm_config *config, + char *txdmachan, char *rxdmachan); +void tegra_pcm_platform_unregister(struct device *dev); + +#endif diff --git a/sound/soc/tegra/tegra_rt5640.c b/sound/soc/tegra/tegra_rt5640.c new file mode 100644 index 000000000..6c2689f5d --- /dev/null +++ b/sound/soc/tegra/tegra_rt5640.c @@ -0,0 +1,225 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* +* tegra_rt5640.c - Tegra machine ASoC driver for boards using RT5640 codec. + * + * Copyright (c) 2013, NVIDIA CORPORATION. All rights reserved. + * + * Based on code copyright/by: + * + * Copyright (C) 2010-2012 - NVIDIA, Inc. + * Copyright (C) 2011 The AC100 Kernel Team <ac100@lists.lauchpad.net> + * (c) 2009, 2010 Nvidia Graphics Pvt. Ltd. + * Copyright 2007 Wolfson Microelectronics PLC. + */ + +#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/rt5640.h" + +#include "tegra_asoc_utils.h" + +#define DRV_NAME "tegra-snd-rt5640" + +struct tegra_rt5640 { + struct tegra_asoc_utils_data util_data; + int gpio_hp_det; + enum of_gpio_flags gpio_hp_det_flags; +}; + +static int tegra_rt5640_asoc_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); + struct snd_soc_card *card = rtd->card; + struct tegra_rt5640 *machine = snd_soc_card_get_drvdata(card); + int srate, mclk; + int err; + + srate = params_rate(params); + mclk = 256 * srate; + + 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, RT5640_SCLK_S_MCLK, mclk, + SND_SOC_CLOCK_IN); + if (err < 0) { + dev_err(card->dev, "codec_dai clock not set\n"); + return err; + } + + return 0; +} + +static const struct snd_soc_ops tegra_rt5640_ops = { + .hw_params = tegra_rt5640_asoc_hw_params, +}; + +static struct snd_soc_jack tegra_rt5640_hp_jack; + +static struct snd_soc_jack_pin tegra_rt5640_hp_jack_pins[] = { + { + .pin = "Headphones", + .mask = SND_JACK_HEADPHONE, + }, +}; + +static struct snd_soc_jack_gpio tegra_rt5640_hp_jack_gpio = { + .name = "Headphone detection", + .report = SND_JACK_HEADPHONE, + .debounce_time = 150, + .invert = 1, +}; + +static const struct snd_soc_dapm_widget tegra_rt5640_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphones", NULL), + SND_SOC_DAPM_SPK("Speakers", NULL), + SND_SOC_DAPM_MIC("Mic Jack", NULL), +}; + +static const struct snd_kcontrol_new tegra_rt5640_controls[] = { + SOC_DAPM_PIN_SWITCH("Speakers"), +}; + +static int tegra_rt5640_asoc_init(struct snd_soc_pcm_runtime *rtd) +{ + struct tegra_rt5640 *machine = snd_soc_card_get_drvdata(rtd->card); + + snd_soc_card_jack_new(rtd->card, "Headphones", SND_JACK_HEADPHONE, + &tegra_rt5640_hp_jack, tegra_rt5640_hp_jack_pins, + ARRAY_SIZE(tegra_rt5640_hp_jack_pins)); + + if (gpio_is_valid(machine->gpio_hp_det)) { + tegra_rt5640_hp_jack_gpio.gpio = machine->gpio_hp_det; + tegra_rt5640_hp_jack_gpio.invert = + !!(machine->gpio_hp_det_flags & OF_GPIO_ACTIVE_LOW); + snd_soc_jack_add_gpios(&tegra_rt5640_hp_jack, + 1, + &tegra_rt5640_hp_jack_gpio); + } + + return 0; +} + +SND_SOC_DAILINK_DEFS(aif1, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "rt5640-aif1")), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +static struct snd_soc_dai_link tegra_rt5640_dai = { + .name = "RT5640", + .stream_name = "RT5640 PCM", + .init = tegra_rt5640_asoc_init, + .ops = &tegra_rt5640_ops, + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + SND_SOC_DAILINK_REG(aif1), +}; + +static struct snd_soc_card snd_soc_tegra_rt5640 = { + .name = "tegra-rt5640", + .driver_name = "tegra", + .owner = THIS_MODULE, + .dai_link = &tegra_rt5640_dai, + .num_links = 1, + .controls = tegra_rt5640_controls, + .num_controls = ARRAY_SIZE(tegra_rt5640_controls), + .dapm_widgets = tegra_rt5640_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(tegra_rt5640_dapm_widgets), + .fully_routed = true, +}; + +static int tegra_rt5640_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct snd_soc_card *card = &snd_soc_tegra_rt5640; + struct tegra_rt5640 *machine; + int ret; + + machine = devm_kzalloc(&pdev->dev, + sizeof(struct tegra_rt5640), GFP_KERNEL); + if (!machine) + return -ENOMEM; + + card->dev = &pdev->dev; + snd_soc_card_set_drvdata(card, machine); + + machine->gpio_hp_det = of_get_named_gpio_flags( + np, "nvidia,hp-det-gpios", 0, &machine->gpio_hp_det_flags); + if (machine->gpio_hp_det == -EPROBE_DEFER) + return -EPROBE_DEFER; + + ret = snd_soc_of_parse_card_name(card, "nvidia,model"); + if (ret) + return ret; + + ret = snd_soc_of_parse_audio_routing(card, "nvidia,audio-routing"); + if (ret) + return ret; + + tegra_rt5640_dai.codecs->of_node = of_parse_phandle(np, + "nvidia,audio-codec", 0); + if (!tegra_rt5640_dai.codecs->of_node) { + dev_err(&pdev->dev, + "Property 'nvidia,audio-codec' missing or invalid\n"); + return -EINVAL; + } + + tegra_rt5640_dai.cpus->of_node = of_parse_phandle(np, + "nvidia,i2s-controller", 0); + if (!tegra_rt5640_dai.cpus->of_node) { + dev_err(&pdev->dev, + "Property 'nvidia,i2s-controller' missing or invalid\n"); + return -EINVAL; + } + + tegra_rt5640_dai.platforms->of_node = tegra_rt5640_dai.cpus->of_node; + + ret = tegra_asoc_utils_init(&machine->util_data, &pdev->dev); + if (ret) + return ret; + + ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", + ret); + return ret; + } + + return 0; +} + +static const struct of_device_id tegra_rt5640_of_match[] = { + { .compatible = "nvidia,tegra-audio-rt5640", }, + {}, +}; + +static struct platform_driver tegra_rt5640_driver = { + .driver = { + .name = DRV_NAME, + .pm = &snd_soc_pm_ops, + .of_match_table = tegra_rt5640_of_match, + }, + .probe = tegra_rt5640_probe, +}; +module_platform_driver(tegra_rt5640_driver); + +MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>"); +MODULE_DESCRIPTION("Tegra+RT5640 machine ASoC driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRV_NAME); +MODULE_DEVICE_TABLE(of, tegra_rt5640_of_match); diff --git a/sound/soc/tegra/tegra_rt5677.c b/sound/soc/tegra/tegra_rt5677.c new file mode 100644 index 000000000..0588889d0 --- /dev/null +++ b/sound/soc/tegra/tegra_rt5677.c @@ -0,0 +1,325 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* +* tegra_rt5677.c - Tegra machine ASoC driver for boards using RT5677 codec. + * + * Copyright (c) 2014, The Chromium OS Authors. All rights reserved. + * + * Based on code copyright/by: + * + * Copyright (C) 2010-2012 - NVIDIA, Inc. + * Copyright (C) 2011 The AC100 Kernel Team <ac100@lists.lauchpad.net> + * (c) 2009, 2010 Nvidia Graphics Pvt. Ltd. + * Copyright 2007 Wolfson Microelectronics PLC. + */ + +#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/rt5677.h" + +#include "tegra_asoc_utils.h" + +#define DRV_NAME "tegra-snd-rt5677" + +struct tegra_rt5677 { + struct tegra_asoc_utils_data util_data; + int gpio_hp_det; + int gpio_hp_en; + int gpio_mic_present; + int gpio_dmic_clk_en; +}; + +static int tegra_rt5677_asoc_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); + struct snd_soc_card *card = rtd->card; + struct tegra_rt5677 *machine = snd_soc_card_get_drvdata(card); + int srate, mclk, err; + + srate = params_rate(params); + mclk = 256 * srate; + + 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, RT5677_SCLK_S_MCLK, mclk, + SND_SOC_CLOCK_IN); + if (err < 0) { + dev_err(card->dev, "codec_dai clock not set\n"); + return err; + } + + return 0; +} + +static int tegra_rt5677_event_hp(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + struct snd_soc_dapm_context *dapm = w->dapm; + struct snd_soc_card *card = dapm->card; + struct tegra_rt5677 *machine = snd_soc_card_get_drvdata(card); + + if (!gpio_is_valid(machine->gpio_hp_en)) + return 0; + + gpio_set_value_cansleep(machine->gpio_hp_en, + SND_SOC_DAPM_EVENT_ON(event)); + + return 0; +} + +static const struct snd_soc_ops tegra_rt5677_ops = { + .hw_params = tegra_rt5677_asoc_hw_params, +}; + +static struct snd_soc_jack tegra_rt5677_hp_jack; + +static struct snd_soc_jack_pin tegra_rt5677_hp_jack_pins = { + .pin = "Headphone", + .mask = SND_JACK_HEADPHONE, +}; +static struct snd_soc_jack_gpio tegra_rt5677_hp_jack_gpio = { + .name = "Headphone detection", + .report = SND_JACK_HEADPHONE, + .debounce_time = 150, +}; + +static struct snd_soc_jack tegra_rt5677_mic_jack; + +static struct snd_soc_jack_pin tegra_rt5677_mic_jack_pins = { + .pin = "Headset Mic", + .mask = SND_JACK_MICROPHONE, +}; + +static struct snd_soc_jack_gpio tegra_rt5677_mic_jack_gpio = { + .name = "Headset Mic detection", + .report = SND_JACK_MICROPHONE, + .debounce_time = 150, + .invert = 1 +}; + +static const struct snd_soc_dapm_widget tegra_rt5677_dapm_widgets[] = { + SND_SOC_DAPM_SPK("Speaker", NULL), + SND_SOC_DAPM_HP("Headphone", tegra_rt5677_event_hp), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_MIC("Internal Mic 1", NULL), + SND_SOC_DAPM_MIC("Internal Mic 2", NULL), +}; + +static const struct snd_kcontrol_new tegra_rt5677_controls[] = { + SOC_DAPM_PIN_SWITCH("Speaker"), + SOC_DAPM_PIN_SWITCH("Headphone"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), + SOC_DAPM_PIN_SWITCH("Internal Mic 1"), + SOC_DAPM_PIN_SWITCH("Internal Mic 2"), +}; + +static int tegra_rt5677_asoc_init(struct snd_soc_pcm_runtime *rtd) +{ + struct tegra_rt5677 *machine = snd_soc_card_get_drvdata(rtd->card); + + snd_soc_card_jack_new(rtd->card, "Headphone Jack", SND_JACK_HEADPHONE, + &tegra_rt5677_hp_jack, + &tegra_rt5677_hp_jack_pins, 1); + + if (gpio_is_valid(machine->gpio_hp_det)) { + tegra_rt5677_hp_jack_gpio.gpio = machine->gpio_hp_det; + snd_soc_jack_add_gpios(&tegra_rt5677_hp_jack, 1, + &tegra_rt5677_hp_jack_gpio); + } + + + snd_soc_card_jack_new(rtd->card, "Mic Jack", SND_JACK_MICROPHONE, + &tegra_rt5677_mic_jack, + &tegra_rt5677_mic_jack_pins, 1); + + if (gpio_is_valid(machine->gpio_mic_present)) { + tegra_rt5677_mic_jack_gpio.gpio = machine->gpio_mic_present; + snd_soc_jack_add_gpios(&tegra_rt5677_mic_jack, 1, + &tegra_rt5677_mic_jack_gpio); + } + + snd_soc_dapm_force_enable_pin(&rtd->card->dapm, "MICBIAS1"); + + return 0; +} + +SND_SOC_DAILINK_DEFS(pcm, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "rt5677-aif1")), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +static struct snd_soc_dai_link tegra_rt5677_dai = { + .name = "RT5677", + .stream_name = "RT5677 PCM", + .init = tegra_rt5677_asoc_init, + .ops = &tegra_rt5677_ops, + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + SND_SOC_DAILINK_REG(pcm), +}; + +static struct snd_soc_card snd_soc_tegra_rt5677 = { + .name = "tegra-rt5677", + .driver_name = "tegra", + .owner = THIS_MODULE, + .dai_link = &tegra_rt5677_dai, + .num_links = 1, + .controls = tegra_rt5677_controls, + .num_controls = ARRAY_SIZE(tegra_rt5677_controls), + .dapm_widgets = tegra_rt5677_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(tegra_rt5677_dapm_widgets), + .fully_routed = true, +}; + +static int tegra_rt5677_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct snd_soc_card *card = &snd_soc_tegra_rt5677; + struct tegra_rt5677 *machine; + int ret; + + machine = devm_kzalloc(&pdev->dev, + sizeof(struct tegra_rt5677), GFP_KERNEL); + if (!machine) + return -ENOMEM; + + card->dev = &pdev->dev; + snd_soc_card_set_drvdata(card, machine); + + machine->gpio_hp_det = of_get_named_gpio(np, "nvidia,hp-det-gpios", 0); + if (machine->gpio_hp_det == -EPROBE_DEFER) + return -EPROBE_DEFER; + + machine->gpio_mic_present = of_get_named_gpio(np, + "nvidia,mic-present-gpios", 0); + if (machine->gpio_mic_present == -EPROBE_DEFER) + return -EPROBE_DEFER; + + machine->gpio_hp_en = of_get_named_gpio(np, "nvidia,hp-en-gpios", 0); + if (machine->gpio_hp_en == -EPROBE_DEFER) + return -EPROBE_DEFER; + if (gpio_is_valid(machine->gpio_hp_en)) { + ret = devm_gpio_request_one(&pdev->dev, machine->gpio_hp_en, + GPIOF_OUT_INIT_LOW, "hp_en"); + if (ret) { + dev_err(card->dev, "cannot get hp_en gpio\n"); + return ret; + } + } + + machine->gpio_dmic_clk_en = of_get_named_gpio(np, + "nvidia,dmic-clk-en-gpios", 0); + if (machine->gpio_dmic_clk_en == -EPROBE_DEFER) + return -EPROBE_DEFER; + if (gpio_is_valid(machine->gpio_dmic_clk_en)) { + ret = devm_gpio_request_one(&pdev->dev, + machine->gpio_dmic_clk_en, + GPIOF_OUT_INIT_HIGH, "dmic_clk_en"); + if (ret) { + dev_err(card->dev, "cannot get dmic_clk_en gpio\n"); + return ret; + } + } + + 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_rt5677_dai.codecs->of_node = of_parse_phandle(np, + "nvidia,audio-codec", 0); + if (!tegra_rt5677_dai.codecs->of_node) { + dev_err(&pdev->dev, + "Property 'nvidia,audio-codec' missing or invalid\n"); + ret = -EINVAL; + goto err; + } + + tegra_rt5677_dai.cpus->of_node = of_parse_phandle(np, + "nvidia,i2s-controller", 0); + if (!tegra_rt5677_dai.cpus->of_node) { + dev_err(&pdev->dev, + "Property 'nvidia,i2s-controller' missing or invalid\n"); + ret = -EINVAL; + goto err_put_codec_of_node; + } + tegra_rt5677_dai.platforms->of_node = tegra_rt5677_dai.cpus->of_node; + + ret = tegra_asoc_utils_init(&machine->util_data, &pdev->dev); + if (ret) + goto err_put_cpu_of_node; + + ret = snd_soc_register_card(card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", + ret); + goto err_put_cpu_of_node; + } + + return 0; + +err_put_cpu_of_node: + of_node_put(tegra_rt5677_dai.cpus->of_node); + tegra_rt5677_dai.cpus->of_node = NULL; + tegra_rt5677_dai.platforms->of_node = NULL; +err_put_codec_of_node: + of_node_put(tegra_rt5677_dai.codecs->of_node); + tegra_rt5677_dai.codecs->of_node = NULL; +err: + return ret; +} + +static int tegra_rt5677_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + + snd_soc_unregister_card(card); + + tegra_rt5677_dai.platforms->of_node = NULL; + of_node_put(tegra_rt5677_dai.codecs->of_node); + tegra_rt5677_dai.codecs->of_node = NULL; + of_node_put(tegra_rt5677_dai.cpus->of_node); + tegra_rt5677_dai.cpus->of_node = NULL; + + return 0; +} + +static const struct of_device_id tegra_rt5677_of_match[] = { + { .compatible = "nvidia,tegra-audio-rt5677", }, + {}, +}; + +static struct platform_driver tegra_rt5677_driver = { + .driver = { + .name = DRV_NAME, + .pm = &snd_soc_pm_ops, + .of_match_table = tegra_rt5677_of_match, + }, + .probe = tegra_rt5677_probe, + .remove = tegra_rt5677_remove, +}; +module_platform_driver(tegra_rt5677_driver); + +MODULE_AUTHOR("Anatol Pomozov <anatol@google.com>"); +MODULE_DESCRIPTION("Tegra+RT5677 machine ASoC driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRV_NAME); +MODULE_DEVICE_TABLE(of, tegra_rt5677_of_match); diff --git a/sound/soc/tegra/tegra_sgtl5000.c b/sound/soc/tegra/tegra_sgtl5000.c new file mode 100644 index 000000000..3d35a57d8 --- /dev/null +++ b/sound/soc/tegra/tegra_sgtl5000.c @@ -0,0 +1,212 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * tegra_sgtl5000.c - Tegra machine ASoC driver for boards using SGTL5000 codec + * + * Author: Marcel Ziswiler <marcel@ziswiler.com> + * + * Based on code copyright/by: + * + * Copyright (C) 2010-2012 - NVIDIA, Inc. + * (c) 2009, 2010 Nvidia Graphics Pvt. Ltd. + * Copyright 2007 Wolfson Microelectronics PLC. + */ + +#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/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> + +#include "../codecs/sgtl5000.h" + +#include "tegra_asoc_utils.h" + +#define DRV_NAME "tegra-snd-sgtl5000" + +struct tegra_sgtl5000 { + struct tegra_asoc_utils_data util_data; +}; + +static int tegra_sgtl5000_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); + struct snd_soc_card *card = rtd->card; + struct tegra_sgtl5000 *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, SGTL5000_SYSCLK, mclk, + SND_SOC_CLOCK_IN); + if (err < 0) { + dev_err(card->dev, "codec_dai clock not set\n"); + return err; + } + + return 0; +} + +static const struct snd_soc_ops tegra_sgtl5000_ops = { + .hw_params = tegra_sgtl5000_hw_params, +}; + +static const struct snd_soc_dapm_widget tegra_sgtl5000_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_LINE("Line In Jack", NULL), + SND_SOC_DAPM_MIC("Mic Jack", NULL), +}; + +SND_SOC_DAILINK_DEFS(hifi, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "sgtl5000")), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +static struct snd_soc_dai_link tegra_sgtl5000_dai = { + .name = "sgtl5000", + .stream_name = "HiFi", + .ops = &tegra_sgtl5000_ops, + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + SND_SOC_DAILINK_REG(hifi), +}; + +static struct snd_soc_card snd_soc_tegra_sgtl5000 = { + .name = "tegra-sgtl5000", + .driver_name = "tegra", + .owner = THIS_MODULE, + .dai_link = &tegra_sgtl5000_dai, + .num_links = 1, + .dapm_widgets = tegra_sgtl5000_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(tegra_sgtl5000_dapm_widgets), + .fully_routed = true, +}; + +static int tegra_sgtl5000_driver_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct snd_soc_card *card = &snd_soc_tegra_sgtl5000; + struct tegra_sgtl5000 *machine; + int ret; + + machine = devm_kzalloc(&pdev->dev, sizeof(struct tegra_sgtl5000), + GFP_KERNEL); + if (!machine) + return -ENOMEM; + + card->dev = &pdev->dev; + 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_sgtl5000_dai.codecs->of_node = of_parse_phandle(np, + "nvidia,audio-codec", 0); + if (!tegra_sgtl5000_dai.codecs->of_node) { + dev_err(&pdev->dev, + "Property 'nvidia,audio-codec' missing or invalid\n"); + ret = -EINVAL; + goto err; + } + + tegra_sgtl5000_dai.cpus->of_node = of_parse_phandle(np, + "nvidia,i2s-controller", 0); + if (!tegra_sgtl5000_dai.cpus->of_node) { + dev_err(&pdev->dev, + "Property 'nvidia,i2s-controller' missing/invalid\n"); + ret = -EINVAL; + goto err_put_codec_of_node; + } + + tegra_sgtl5000_dai.platforms->of_node = tegra_sgtl5000_dai.cpus->of_node; + + ret = tegra_asoc_utils_init(&machine->util_data, &pdev->dev); + if (ret) + goto err_put_cpu_of_node; + + ret = snd_soc_register_card(card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", + ret); + goto err_put_cpu_of_node; + } + + return 0; + +err_put_cpu_of_node: + of_node_put(tegra_sgtl5000_dai.cpus->of_node); + tegra_sgtl5000_dai.cpus->of_node = NULL; + tegra_sgtl5000_dai.platforms->of_node = NULL; +err_put_codec_of_node: + of_node_put(tegra_sgtl5000_dai.codecs->of_node); + tegra_sgtl5000_dai.codecs->of_node = NULL; +err: + return ret; +} + +static int tegra_sgtl5000_driver_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + int ret; + + ret = snd_soc_unregister_card(card); + + of_node_put(tegra_sgtl5000_dai.cpus->of_node); + tegra_sgtl5000_dai.cpus->of_node = NULL; + tegra_sgtl5000_dai.platforms->of_node = NULL; + of_node_put(tegra_sgtl5000_dai.codecs->of_node); + tegra_sgtl5000_dai.codecs->of_node = NULL; + + return ret; +} + +static const struct of_device_id tegra_sgtl5000_of_match[] = { + { .compatible = "nvidia,tegra-audio-sgtl5000", }, + { /* sentinel */ }, +}; + +static struct platform_driver tegra_sgtl5000_driver = { + .driver = { + .name = DRV_NAME, + .pm = &snd_soc_pm_ops, + .of_match_table = tegra_sgtl5000_of_match, + }, + .probe = tegra_sgtl5000_driver_probe, + .remove = tegra_sgtl5000_driver_remove, +}; +module_platform_driver(tegra_sgtl5000_driver); + +MODULE_AUTHOR("Marcel Ziswiler <marcel@ziswiler.com>"); +MODULE_DESCRIPTION("Tegra SGTL5000 machine ASoC driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRV_NAME); +MODULE_DEVICE_TABLE(of, tegra_sgtl5000_of_match); diff --git a/sound/soc/tegra/tegra_wm8753.c b/sound/soc/tegra/tegra_wm8753.c new file mode 100644 index 000000000..bdddda4eb --- /dev/null +++ b/sound/soc/tegra/tegra_wm8753.c @@ -0,0 +1,188 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * 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 + */ + +#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 = asoc_substream_to_rtd(substream); + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); + struct snd_soc_card *card = rtd->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 const 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), +}; + +SND_SOC_DAILINK_DEFS(pcm, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "wm8753-hifi")), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +static struct snd_soc_dai_link tegra_wm8753_dai = { + .name = "WM8753", + .stream_name = "WM8753 PCM", + .ops = &tegra_wm8753_ops, + .dai_fmt = SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + SND_SOC_DAILINK_REG(pcm), +}; + +static struct snd_soc_card snd_soc_tegra_wm8753 = { + .name = "tegra-wm8753", + .driver_name = "tegra", + .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 int tegra_wm8753_driver_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + 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) + return -ENOMEM; + + card->dev = &pdev->dev; + snd_soc_card_set_drvdata(card, machine); + + ret = snd_soc_of_parse_card_name(card, "nvidia,model"); + if (ret) + return ret; + + ret = snd_soc_of_parse_audio_routing(card, "nvidia,audio-routing"); + if (ret) + return ret; + + tegra_wm8753_dai.codecs->of_node = of_parse_phandle(np, + "nvidia,audio-codec", 0); + if (!tegra_wm8753_dai.codecs->of_node) { + dev_err(&pdev->dev, + "Property 'nvidia,audio-codec' missing or invalid\n"); + return -EINVAL; + } + + tegra_wm8753_dai.cpus->of_node = of_parse_phandle(np, + "nvidia,i2s-controller", 0); + if (!tegra_wm8753_dai.cpus->of_node) { + dev_err(&pdev->dev, + "Property 'nvidia,i2s-controller' missing or invalid\n"); + return -EINVAL; + } + + tegra_wm8753_dai.platforms->of_node = tegra_wm8753_dai.cpus->of_node; + + ret = tegra_asoc_utils_init(&machine->util_data, &pdev->dev); + if (ret) + return ret; + + ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", + ret); + return ret; + } + + return 0; +} + +static const struct of_device_id tegra_wm8753_of_match[] = { + { .compatible = "nvidia,tegra-audio-wm8753", }, + {}, +}; + +static struct platform_driver tegra_wm8753_driver = { + .driver = { + .name = DRV_NAME, + .pm = &snd_soc_pm_ops, + .of_match_table = tegra_wm8753_of_match, + }, + .probe = tegra_wm8753_driver_probe, +}; +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 new file mode 100644 index 000000000..98adf93fb --- /dev/null +++ b/sound/soc/tegra/tegra_wm8903.c @@ -0,0 +1,384 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * tegra_wm8903.c - Tegra machine ASoC driver for boards using WM8903 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 + */ + +#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/wm8903.h" + +#include "tegra_asoc_utils.h" + +#define DRV_NAME "tegra-snd-wm8903" + +struct tegra_wm8903 { + int gpio_spkr_en; + int gpio_hp_det; + int gpio_hp_mute; + int gpio_int_mic_en; + int gpio_ext_mic_en; + struct tegra_asoc_utils_data util_data; +}; + +static int tegra_wm8903_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); + struct snd_soc_card *card = rtd->card; + struct tegra_wm8903 *machine = snd_soc_card_get_drvdata(card); + int srate, mclk; + int err; + + srate = params_rate(params); + switch (srate) { + case 64000: + case 88200: + case 96000: + mclk = 128 * srate; + break; + default: + mclk = 256 * srate; + break; + } + /* FIXME: Codec only requires >= 3MHz if OSR==0 */ + while (mclk < 6000000) + mclk *= 2; + + 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, 0, mclk, + SND_SOC_CLOCK_IN); + if (err < 0) { + dev_err(card->dev, "codec_dai clock not set\n"); + return err; + } + + return 0; +} + +static const struct snd_soc_ops tegra_wm8903_ops = { + .hw_params = tegra_wm8903_hw_params, +}; + +static struct snd_soc_jack tegra_wm8903_hp_jack; + +static struct snd_soc_jack_pin tegra_wm8903_hp_jack_pins[] = { + { + .pin = "Headphone Jack", + .mask = SND_JACK_HEADPHONE, + }, +}; + +static struct snd_soc_jack_gpio tegra_wm8903_hp_jack_gpio = { + .name = "headphone detect", + .report = SND_JACK_HEADPHONE, + .debounce_time = 150, + .invert = 1, +}; + +static struct snd_soc_jack tegra_wm8903_mic_jack; + +static struct snd_soc_jack_pin tegra_wm8903_mic_jack_pins[] = { + { + .pin = "Mic Jack", + .mask = SND_JACK_MICROPHONE, + }, +}; + +static int tegra_wm8903_event_int_spk(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + struct snd_soc_dapm_context *dapm = w->dapm; + struct snd_soc_card *card = dapm->card; + struct tegra_wm8903 *machine = snd_soc_card_get_drvdata(card); + + if (!gpio_is_valid(machine->gpio_spkr_en)) + return 0; + + gpio_set_value_cansleep(machine->gpio_spkr_en, + SND_SOC_DAPM_EVENT_ON(event)); + + return 0; +} + +static int tegra_wm8903_event_hp(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + struct snd_soc_dapm_context *dapm = w->dapm; + struct snd_soc_card *card = dapm->card; + struct tegra_wm8903 *machine = snd_soc_card_get_drvdata(card); + + if (!gpio_is_valid(machine->gpio_hp_mute)) + return 0; + + gpio_set_value_cansleep(machine->gpio_hp_mute, + !SND_SOC_DAPM_EVENT_ON(event)); + + return 0; +} + +static int tegra_wm8903_event_int_mic(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + struct snd_soc_dapm_context *dapm = w->dapm; + struct snd_soc_card *card = dapm->card; + struct tegra_wm8903 *machine = snd_soc_card_get_drvdata(card); + + if (!gpio_is_valid(machine->gpio_int_mic_en)) + return 0; + + gpio_set_value_cansleep(machine->gpio_int_mic_en, + SND_SOC_DAPM_EVENT_ON(event)); + + return 0; +} + +static const struct snd_soc_dapm_widget tegra_wm8903_dapm_widgets[] = { + SND_SOC_DAPM_SPK("Int Spk", tegra_wm8903_event_int_spk), + SND_SOC_DAPM_HP("Headphone Jack", tegra_wm8903_event_hp), + SND_SOC_DAPM_MIC("Mic Jack", NULL), + SND_SOC_DAPM_MIC("Int Mic", tegra_wm8903_event_int_mic), +}; + +static const struct snd_kcontrol_new tegra_wm8903_controls[] = { + SOC_DAPM_PIN_SWITCH("Int Spk"), + SOC_DAPM_PIN_SWITCH("Int Mic"), +}; + +static int tegra_wm8903_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); + struct snd_soc_component *component = codec_dai->component; + struct snd_soc_card *card = rtd->card; + struct tegra_wm8903 *machine = snd_soc_card_get_drvdata(card); + int shrt = 0; + + if (gpio_is_valid(machine->gpio_hp_det)) { + tegra_wm8903_hp_jack_gpio.gpio = machine->gpio_hp_det; + snd_soc_card_jack_new(rtd->card, "Headphone Jack", + SND_JACK_HEADPHONE, &tegra_wm8903_hp_jack, + tegra_wm8903_hp_jack_pins, + ARRAY_SIZE(tegra_wm8903_hp_jack_pins)); + snd_soc_jack_add_gpios(&tegra_wm8903_hp_jack, + 1, + &tegra_wm8903_hp_jack_gpio); + } + + if (of_property_read_bool(card->dev->of_node, "nvidia,headset")) + shrt = SND_JACK_MICROPHONE; + + snd_soc_card_jack_new(rtd->card, "Mic Jack", SND_JACK_MICROPHONE, + &tegra_wm8903_mic_jack, + tegra_wm8903_mic_jack_pins, + ARRAY_SIZE(tegra_wm8903_mic_jack_pins)); + wm8903_mic_detect(component, &tegra_wm8903_mic_jack, SND_JACK_MICROPHONE, + shrt); + + snd_soc_dapm_force_enable_pin(&card->dapm, "MICBIAS"); + + return 0; +} + +static int tegra_wm8903_remove(struct snd_soc_card *card) +{ + struct snd_soc_pcm_runtime *rtd = + snd_soc_get_pcm_runtime(card, &card->dai_link[0]); + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); + struct snd_soc_component *component = codec_dai->component; + + wm8903_mic_detect(component, NULL, 0, 0); + + return 0; +} + +SND_SOC_DAILINK_DEFS(hifi, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "wm8903-hifi")), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +static struct snd_soc_dai_link tegra_wm8903_dai = { + .name = "WM8903", + .stream_name = "WM8903 PCM", + .init = tegra_wm8903_init, + .ops = &tegra_wm8903_ops, + .dai_fmt = SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + SND_SOC_DAILINK_REG(hifi), +}; + +static struct snd_soc_card snd_soc_tegra_wm8903 = { + .name = "tegra-wm8903", + .driver_name = "tegra", + .owner = THIS_MODULE, + .dai_link = &tegra_wm8903_dai, + .num_links = 1, + .remove = tegra_wm8903_remove, + .controls = tegra_wm8903_controls, + .num_controls = ARRAY_SIZE(tegra_wm8903_controls), + .dapm_widgets = tegra_wm8903_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(tegra_wm8903_dapm_widgets), + .fully_routed = true, +}; + +static int tegra_wm8903_driver_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct snd_soc_card *card = &snd_soc_tegra_wm8903; + struct tegra_wm8903 *machine; + int ret; + + machine = devm_kzalloc(&pdev->dev, sizeof(struct tegra_wm8903), + GFP_KERNEL); + if (!machine) + return -ENOMEM; + + card->dev = &pdev->dev; + snd_soc_card_set_drvdata(card, machine); + + machine->gpio_spkr_en = of_get_named_gpio(np, "nvidia,spkr-en-gpios", + 0); + if (machine->gpio_spkr_en == -EPROBE_DEFER) + return -EPROBE_DEFER; + if (gpio_is_valid(machine->gpio_spkr_en)) { + ret = devm_gpio_request_one(&pdev->dev, machine->gpio_spkr_en, + GPIOF_OUT_INIT_LOW, "spkr_en"); + if (ret) { + dev_err(card->dev, "cannot get spkr_en gpio\n"); + return ret; + } + } + + machine->gpio_hp_mute = of_get_named_gpio(np, "nvidia,hp-mute-gpios", + 0); + if (machine->gpio_hp_mute == -EPROBE_DEFER) + return -EPROBE_DEFER; + if (gpio_is_valid(machine->gpio_hp_mute)) { + ret = devm_gpio_request_one(&pdev->dev, machine->gpio_hp_mute, + GPIOF_OUT_INIT_HIGH, "hp_mute"); + if (ret) { + dev_err(card->dev, "cannot get hp_mute gpio\n"); + return ret; + } + } + + machine->gpio_hp_det = of_get_named_gpio(np, "nvidia,hp-det-gpios", 0); + if (machine->gpio_hp_det == -EPROBE_DEFER) + return -EPROBE_DEFER; + + machine->gpio_int_mic_en = of_get_named_gpio(np, + "nvidia,int-mic-en-gpios", 0); + if (machine->gpio_int_mic_en == -EPROBE_DEFER) + return -EPROBE_DEFER; + if (gpio_is_valid(machine->gpio_int_mic_en)) { + /* Disable int mic; enable signal is active-high */ + ret = devm_gpio_request_one(&pdev->dev, + machine->gpio_int_mic_en, + GPIOF_OUT_INIT_LOW, "int_mic_en"); + if (ret) { + dev_err(card->dev, "cannot get int_mic_en gpio\n"); + return ret; + } + } + + machine->gpio_ext_mic_en = of_get_named_gpio(np, + "nvidia,ext-mic-en-gpios", 0); + if (machine->gpio_ext_mic_en == -EPROBE_DEFER) + return -EPROBE_DEFER; + if (gpio_is_valid(machine->gpio_ext_mic_en)) { + /* Enable ext mic; enable signal is active-low */ + ret = devm_gpio_request_one(&pdev->dev, + machine->gpio_ext_mic_en, + GPIOF_OUT_INIT_LOW, "ext_mic_en"); + if (ret) { + dev_err(card->dev, "cannot get ext_mic_en gpio\n"); + return ret; + } + } + + ret = snd_soc_of_parse_card_name(card, "nvidia,model"); + if (ret) + return ret; + + ret = snd_soc_of_parse_audio_routing(card, "nvidia,audio-routing"); + if (ret) + return ret; + + tegra_wm8903_dai.codecs->of_node = of_parse_phandle(np, + "nvidia,audio-codec", 0); + if (!tegra_wm8903_dai.codecs->of_node) { + dev_err(&pdev->dev, + "Property 'nvidia,audio-codec' missing or invalid\n"); + return -EINVAL; + } + + tegra_wm8903_dai.cpus->of_node = of_parse_phandle(np, + "nvidia,i2s-controller", 0); + if (!tegra_wm8903_dai.cpus->of_node) { + dev_err(&pdev->dev, + "Property 'nvidia,i2s-controller' missing or invalid\n"); + return -EINVAL; + } + + tegra_wm8903_dai.platforms->of_node = tegra_wm8903_dai.cpus->of_node; + + ret = tegra_asoc_utils_init(&machine->util_data, &pdev->dev); + if (ret) + return ret; + + ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret) { + dev_err(&pdev->dev, "devm_snd_soc_register_card failed (%d)\n", + ret); + return ret; + } + + return 0; +} + +static const struct of_device_id tegra_wm8903_of_match[] = { + { .compatible = "nvidia,tegra-audio-wm8903", }, + {}, +}; + +static struct platform_driver tegra_wm8903_driver = { + .driver = { + .name = DRV_NAME, + .pm = &snd_soc_pm_ops, + .of_match_table = tegra_wm8903_of_match, + }, + .probe = tegra_wm8903_driver_probe, +}; +module_platform_driver(tegra_wm8903_driver); + +MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>"); +MODULE_DESCRIPTION("Tegra+WM8903 machine ASoC driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRV_NAME); +MODULE_DEVICE_TABLE(of, tegra_wm8903_of_match); diff --git a/sound/soc/tegra/tegra_wm9712.c b/sound/soc/tegra/tegra_wm9712.c new file mode 100644 index 000000000..df7662258 --- /dev/null +++ b/sound/soc/tegra/tegra_wm9712.c @@ -0,0 +1,167 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * tegra20_wm9712.c - Tegra machine ASoC driver for boards using WM9712 codec. + * + * Copyright 2012 Lucas Stach <dev@lynxeye.de> + * + * Partly based on code copyright/by: + * Copyright 2011,2012 Toradex Inc. + */ + +#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 "tegra_asoc_utils.h" + +#define DRV_NAME "tegra-snd-wm9712" + +struct tegra_wm9712 { + struct platform_device *codec; + struct tegra_asoc_utils_data util_data; +}; + +static const struct snd_soc_dapm_widget tegra_wm9712_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone", NULL), + SND_SOC_DAPM_LINE("LineIn", NULL), + SND_SOC_DAPM_MIC("Mic", NULL), +}; + +static int tegra_wm9712_init(struct snd_soc_pcm_runtime *rtd) +{ + return snd_soc_dapm_force_enable_pin(&rtd->card->dapm, "Mic Bias"); +} + +SND_SOC_DAILINK_DEFS(hifi, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_CODEC("wm9712-codec", "wm9712-hifi")), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +static struct snd_soc_dai_link tegra_wm9712_dai = { + .name = "AC97 HiFi", + .stream_name = "AC97 HiFi", + .init = tegra_wm9712_init, + SND_SOC_DAILINK_REG(hifi), +}; + +static struct snd_soc_card snd_soc_tegra_wm9712 = { + .name = "tegra-wm9712", + .driver_name = "tegra", + .owner = THIS_MODULE, + .dai_link = &tegra_wm9712_dai, + .num_links = 1, + + .dapm_widgets = tegra_wm9712_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(tegra_wm9712_dapm_widgets), + .fully_routed = true, +}; + +static int tegra_wm9712_driver_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct snd_soc_card *card = &snd_soc_tegra_wm9712; + struct tegra_wm9712 *machine; + int ret; + + machine = devm_kzalloc(&pdev->dev, sizeof(struct tegra_wm9712), + GFP_KERNEL); + if (!machine) + return -ENOMEM; + + card->dev = &pdev->dev; + snd_soc_card_set_drvdata(card, machine); + + machine->codec = platform_device_alloc("wm9712-codec", -1); + if (!machine->codec) { + dev_err(&pdev->dev, "Can't allocate wm9712 platform device\n"); + return -ENOMEM; + } + + ret = platform_device_add(machine->codec); + if (ret) + goto codec_put; + + ret = snd_soc_of_parse_card_name(card, "nvidia,model"); + if (ret) + goto codec_unregister; + + ret = snd_soc_of_parse_audio_routing(card, "nvidia,audio-routing"); + if (ret) + goto codec_unregister; + + tegra_wm9712_dai.cpus->of_node = of_parse_phandle(np, + "nvidia,ac97-controller", 0); + if (!tegra_wm9712_dai.cpus->of_node) { + dev_err(&pdev->dev, + "Property 'nvidia,ac97-controller' missing or invalid\n"); + ret = -EINVAL; + goto codec_unregister; + } + + tegra_wm9712_dai.platforms->of_node = tegra_wm9712_dai.cpus->of_node; + + ret = tegra_asoc_utils_init(&machine->util_data, &pdev->dev); + if (ret) + goto codec_unregister; + + ret = tegra_asoc_utils_set_ac97_rate(&machine->util_data); + if (ret) + goto codec_unregister; + + ret = snd_soc_register_card(card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", + ret); + goto codec_unregister; + } + + return 0; + +codec_unregister: + platform_device_del(machine->codec); +codec_put: + platform_device_put(machine->codec); + return ret; +} + +static int tegra_wm9712_driver_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + struct tegra_wm9712 *machine = snd_soc_card_get_drvdata(card); + + snd_soc_unregister_card(card); + + platform_device_unregister(machine->codec); + + return 0; +} + +static const struct of_device_id tegra_wm9712_of_match[] = { + { .compatible = "nvidia,tegra-audio-wm9712", }, + {}, +}; + +static struct platform_driver tegra_wm9712_driver = { + .driver = { + .name = DRV_NAME, + .pm = &snd_soc_pm_ops, + .of_match_table = tegra_wm9712_of_match, + }, + .probe = tegra_wm9712_driver_probe, + .remove = tegra_wm9712_driver_remove, +}; +module_platform_driver(tegra_wm9712_driver); + +MODULE_AUTHOR("Lucas Stach"); +MODULE_DESCRIPTION("Tegra+WM9712 machine ASoC driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRV_NAME); +MODULE_DEVICE_TABLE(of, tegra_wm9712_of_match); diff --git a/sound/soc/tegra/trimslice.c b/sound/soc/tegra/trimslice.c new file mode 100644 index 000000000..d8fbb2248 --- /dev/null +++ b/sound/soc/tegra/trimslice.c @@ -0,0 +1,175 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * trimslice.c - TrimSlice machine ASoC driver + * + * Copyright (C) 2011 - CompuLab, Ltd. + * Author: Mike Rapoport <mike@compulab.co.il> + * + * Based on code copyright/by: + * Author: Stephen Warren <swarren@nvidia.com> + * Copyright (C) 2010-2011 - NVIDIA, Inc. + */ + +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +#include <sound/core.h> +#include <sound/jack.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> + +#include "../codecs/tlv320aic23.h" + +#include "tegra_asoc_utils.h" + +#define DRV_NAME "tegra-snd-trimslice" + +struct tegra_trimslice { + struct tegra_asoc_utils_data util_data; +}; + +static int trimslice_asoc_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); + struct snd_soc_card *card = rtd->card; + struct tegra_trimslice *trimslice = snd_soc_card_get_drvdata(card); + int srate, mclk; + int err; + + srate = params_rate(params); + mclk = 128 * srate; + + err = tegra_asoc_utils_set_rate(&trimslice->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, 0, mclk, + SND_SOC_CLOCK_IN); + if (err < 0) { + dev_err(card->dev, "codec_dai clock not set\n"); + return err; + } + + return 0; +} + +static const struct snd_soc_ops trimslice_asoc_ops = { + .hw_params = trimslice_asoc_hw_params, +}; + +static const struct snd_soc_dapm_widget trimslice_dapm_widgets[] = { + SND_SOC_DAPM_HP("Line Out", NULL), + SND_SOC_DAPM_LINE("Line In", NULL), +}; + +static const struct snd_soc_dapm_route trimslice_audio_map[] = { + {"Line Out", NULL, "LOUT"}, + {"Line Out", NULL, "ROUT"}, + + {"LLINEIN", NULL, "Line In"}, + {"RLINEIN", NULL, "Line In"}, +}; + +SND_SOC_DAILINK_DEFS(single_dsp, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "tlv320aic23-hifi")), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +static struct snd_soc_dai_link trimslice_tlv320aic23_dai = { + .name = "TLV320AIC23", + .stream_name = "AIC23", + .ops = &trimslice_asoc_ops, + .dai_fmt = SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + SND_SOC_DAILINK_REG(single_dsp), +}; + +static struct snd_soc_card snd_soc_trimslice = { + .name = "tegra-trimslice", + .driver_name = "tegra", + .owner = THIS_MODULE, + .dai_link = &trimslice_tlv320aic23_dai, + .num_links = 1, + + .dapm_widgets = trimslice_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(trimslice_dapm_widgets), + .dapm_routes = trimslice_audio_map, + .num_dapm_routes = ARRAY_SIZE(trimslice_audio_map), + .fully_routed = true, +}; + +static int tegra_snd_trimslice_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct snd_soc_card *card = &snd_soc_trimslice; + struct tegra_trimslice *trimslice; + int ret; + + trimslice = devm_kzalloc(&pdev->dev, sizeof(struct tegra_trimslice), + GFP_KERNEL); + if (!trimslice) + return -ENOMEM; + + card->dev = &pdev->dev; + snd_soc_card_set_drvdata(card, trimslice); + + trimslice_tlv320aic23_dai.codecs->of_node = of_parse_phandle(np, + "nvidia,audio-codec", 0); + if (!trimslice_tlv320aic23_dai.codecs->of_node) { + dev_err(&pdev->dev, + "Property 'nvidia,audio-codec' missing or invalid\n"); + return -EINVAL; + } + + trimslice_tlv320aic23_dai.cpus->of_node = of_parse_phandle(np, + "nvidia,i2s-controller", 0); + if (!trimslice_tlv320aic23_dai.cpus->of_node) { + dev_err(&pdev->dev, + "Property 'nvidia,i2s-controller' missing or invalid\n"); + return -EINVAL; + } + + trimslice_tlv320aic23_dai.platforms->of_node = + trimslice_tlv320aic23_dai.cpus->of_node; + + ret = tegra_asoc_utils_init(&trimslice->util_data, &pdev->dev); + if (ret) + return ret; + + ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", + ret); + return ret; + } + + return 0; +} + +static const struct of_device_id trimslice_of_match[] = { + { .compatible = "nvidia,tegra-audio-trimslice", }, + {}, +}; +MODULE_DEVICE_TABLE(of, trimslice_of_match); + +static struct platform_driver tegra_snd_trimslice_driver = { + .driver = { + .name = DRV_NAME, + .of_match_table = trimslice_of_match, + }, + .probe = tegra_snd_trimslice_probe, +}; +module_platform_driver(tegra_snd_trimslice_driver); + +MODULE_AUTHOR("Mike Rapoport <mike@compulab.co.il>"); +MODULE_DESCRIPTION("Trimslice machine ASoC driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRV_NAME); |