diff options
Diffstat (limited to 'sound/soc/tegra')
27 files changed, 4155 insertions, 16 deletions
diff --git a/sound/soc/tegra/Kconfig b/sound/soc/tegra/Kconfig index addadc827b91..3d91bd3e59cd 100644 --- a/sound/soc/tegra/Kconfig +++ b/sound/soc/tegra/Kconfig @@ -62,6 +62,62 @@ config SND_SOC_TEGRA30_I2S 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 diff --git a/sound/soc/tegra/Makefile b/sound/soc/tegra/Makefile index c84f183919f2..60040a06b814 100644 --- a/sound/soc/tegra/Makefile +++ b/sound/soc/tegra/Makefile @@ -8,6 +8,11 @@ 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 @@ -17,6 +22,11 @@ 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 diff --git a/sound/soc/tegra/tegra186_dspk.c b/sound/soc/tegra/tegra186_dspk.c new file mode 100644 index 000000000000..fe7117171a0e --- /dev/null +++ b/sound/soc/tegra/tegra186_dspk.c @@ -0,0 +1,442 @@ +// 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_control(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); + + if (strstr(kcontrol->id.name, "FIFO Threshold")) + ucontrol->value.integer.value[0] = dspk->rx_fifo_th; + else if (strstr(kcontrol->id.name, "OSR Value")) + ucontrol->value.integer.value[0] = dspk->osr_val; + else if (strstr(kcontrol->id.name, "LR Polarity Select")) + ucontrol->value.integer.value[0] = dspk->lrsel; + else if (strstr(kcontrol->id.name, "Channel Select")) + ucontrol->value.integer.value[0] = dspk->ch_sel; + else if (strstr(kcontrol->id.name, "Mono To Stereo")) + ucontrol->value.integer.value[0] = dspk->mono_to_stereo; + else if (strstr(kcontrol->id.name, "Stereo To Mono")) + ucontrol->value.integer.value[0] = dspk->stereo_to_mono; + + return 0; +} + +static int tegra186_dspk_put_control(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 val = ucontrol->value.integer.value[0]; + + if (strstr(kcontrol->id.name, "FIFO Threshold")) + dspk->rx_fifo_th = val; + else if (strstr(kcontrol->id.name, "OSR Value")) + dspk->osr_val = val; + else if (strstr(kcontrol->id.name, "LR Polarity Select")) + dspk->lrsel = val; + else if (strstr(kcontrol->id.name, "Channel Select")) + dspk->ch_sel = val; + else if (strstr(kcontrol->id.name, "Mono To Stereo")) + dspk->mono_to_stereo = val; + else if (strstr(kcontrol->id.name, "Stereo To Mono")) + dspk->stereo_to_mono = val; + + return 0; +} + +static int 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 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_control, tegra186_dspk_put_control), + SOC_ENUM_EXT("OSR Value", tegra186_dspk_osr_enum, + tegra186_dspk_get_control, tegra186_dspk_put_control), + SOC_ENUM_EXT("LR Polarity Select", tegra186_dspk_lrsel_enum, + tegra186_dspk_get_control, tegra186_dspk_put_control), + SOC_ENUM_EXT("Channel Select", tegra186_dspk_ch_sel_enum, + tegra186_dspk_get_control, tegra186_dspk_put_control), + SOC_ENUM_EXT("Mono To Stereo", tegra186_dspk_mono_conv_enum, + tegra186_dspk_get_control, tegra186_dspk_put_control), + SOC_ENUM_EXT("Stereo To Mono", tegra186_dspk_stereo_conv_enum, + tegra186_dspk_get_control, tegra186_dspk_put_control), +}; + +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 000000000000..b2a879065d3c --- /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_das.c b/sound/soc/tegra/tegra20_das.c index 1070b2710d5e..79dba878d854 100644 --- a/sound/soc/tegra/tegra20_das.c +++ b/sound/soc/tegra/tegra20_das.c @@ -98,8 +98,7 @@ EXPORT_SYMBOL_GPL(tegra20_das_connect_dac_to_dap); static bool tegra20_das_wr_rd_reg(struct device *dev, unsigned int reg) { - if ((reg >= TEGRA20_DAS_DAP_CTRL_SEL) && - (reg <= LAST_REG(DAP_CTRL_SEL))) + 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))) diff --git a/sound/soc/tegra/tegra20_das.h b/sound/soc/tegra/tegra20_das.h index 16b95b770a1d..d22abc4d08e6 100644 --- a/sound/soc/tegra/tegra20_das.h +++ b/sound/soc/tegra/tegra20_das.h @@ -91,14 +91,14 @@ struct tegra20_das { */ /* - * Connect a DAP to to a DAC + * 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 to another DAP + * 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) diff --git a/sound/soc/tegra/tegra210_admaif.c b/sound/soc/tegra/tegra210_admaif.c new file mode 100644 index 000000000000..4894e8e6ee7f --- /dev/null +++ b/sound/soc/tegra/tegra210_admaif.c @@ -0,0 +1,800 @@ +// 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 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 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 tegra_admaif_get_control(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); + struct soc_enum *ec = (struct soc_enum *)kcontrol->private_value; + struct tegra_admaif *admaif = snd_soc_component_get_drvdata(cmpnt); + long *uctl_val = &ucontrol->value.integer.value[0]; + + if (strstr(kcontrol->id.name, "Playback Mono To Stereo")) + *uctl_val = admaif->mono_to_stereo[ADMAIF_TX_PATH][ec->reg]; + else if (strstr(kcontrol->id.name, "Capture Mono To Stereo")) + *uctl_val = admaif->mono_to_stereo[ADMAIF_RX_PATH][ec->reg]; + else if (strstr(kcontrol->id.name, "Playback Stereo To Mono")) + *uctl_val = admaif->stereo_to_mono[ADMAIF_TX_PATH][ec->reg]; + else if (strstr(kcontrol->id.name, "Capture Stereo To Mono")) + *uctl_val = admaif->stereo_to_mono[ADMAIF_RX_PATH][ec->reg]; + + return 0; +} + +static int tegra_admaif_put_control(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); + struct soc_enum *ec = (struct soc_enum *)kcontrol->private_value; + struct tegra_admaif *admaif = snd_soc_component_get_drvdata(cmpnt); + int value = ucontrol->value.integer.value[0]; + + if (strstr(kcontrol->id.name, "Playback Mono To Stereo")) + admaif->mono_to_stereo[ADMAIF_TX_PATH][ec->reg] = value; + else if (strstr(kcontrol->id.name, "Capture Mono To Stereo")) + admaif->mono_to_stereo[ADMAIF_RX_PATH][ec->reg] = value; + else if (strstr(kcontrol->id.name, "Playback Stereo To Mono")) + admaif->stereo_to_mono[ADMAIF_TX_PATH][ec->reg] = value; + else if (strstr(kcontrol->id.name, "Capture Stereo To Mono")) + admaif->stereo_to_mono[ADMAIF_RX_PATH][ec->reg] = value; + + return 0; +} + +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,\ + tegra_admaif_get_control, tegra_admaif_put_control, \ + tegra_admaif_mono_conv_text), \ + NV_SOC_ENUM_EXT("ADMAIF" #reg " Playback Stereo To Mono", reg - 1,\ + tegra_admaif_get_control, tegra_admaif_put_control, \ + tegra_admaif_stereo_conv_text), \ + NV_SOC_ENUM_EXT("ADMAIF" #reg " Capture Mono To Stereo", reg - 1, \ + tegra_admaif_get_control, tegra_admaif_put_control, \ + tegra_admaif_mono_conv_text), \ + NV_SOC_ENUM_EXT("ADMAIF" #reg " Capture Stereo To Mono", reg - 1, \ + tegra_admaif_get_control, tegra_admaif_put_control, \ + 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 000000000000..96686dc92081 --- /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 000000000000..5123a96fdde8 --- /dev/null +++ b/sound/soc/tegra/tegra210_ahub.c @@ -0,0 +1,676 @@ +// 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; + + 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)) + snd_soc_dapm_mux_update_power(dapm, kctl, item[0], e, + &update[i]); + } + + return 0; +} + +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 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 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 000000000000..47802bbe17a9 --- /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 000000000000..d682414ad90d --- /dev/null +++ b/sound/soc/tegra/tegra210_dmic.c @@ -0,0 +1,456 @@ +// 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 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 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_control(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); + + if (strstr(kcontrol->id.name, "Boost Gain Volume")) + ucontrol->value.integer.value[0] = dmic->boost_gain; + else if (strstr(kcontrol->id.name, "Channel Select")) + ucontrol->value.integer.value[0] = dmic->ch_select; + else if (strstr(kcontrol->id.name, "Mono To Stereo")) + ucontrol->value.integer.value[0] = dmic->mono_to_stereo; + else if (strstr(kcontrol->id.name, "Stereo To Mono")) + ucontrol->value.integer.value[0] = dmic->stereo_to_mono; + else if (strstr(kcontrol->id.name, "OSR Value")) + ucontrol->value.integer.value[0] = dmic->osr_val; + else if (strstr(kcontrol->id.name, "LR Polarity Select")) + ucontrol->value.integer.value[0] = dmic->lrsel; + + return 0; +} + +static int tegra210_dmic_put_control(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 (strstr(kcontrol->id.name, "Boost Gain Volume")) + dmic->boost_gain = value; + else if (strstr(kcontrol->id.name, "Channel Select")) + dmic->ch_select = ucontrol->value.integer.value[0]; + else if (strstr(kcontrol->id.name, "Mono To Stereo")) + dmic->mono_to_stereo = value; + else if (strstr(kcontrol->id.name, "Stereo To Mono")) + dmic->stereo_to_mono = value; + else if (strstr(kcontrol->id.name, "OSR Value")) + dmic->osr_val = value; + else if (strstr(kcontrol->id.name, "LR Polarity Select")) + dmic->lrsel = value; + + return 0; +} + +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_control, tegra210_dmic_put_control), + SOC_ENUM_EXT("Channel Select", tegra210_dmic_ch_enum, + tegra210_dmic_get_control, tegra210_dmic_put_control), + SOC_ENUM_EXT("Mono To Stereo", + tegra210_dmic_mono_conv_enum, tegra210_dmic_get_control, + tegra210_dmic_put_control), + SOC_ENUM_EXT("Stereo To Mono", + tegra210_dmic_stereo_conv_enum, tegra210_dmic_get_control, + tegra210_dmic_put_control), + SOC_ENUM_EXT("OSR Value", tegra210_dmic_osr_enum, + tegra210_dmic_get_control, tegra210_dmic_put_control), + SOC_ENUM_EXT("LR Polarity Select", tegra210_dmic_lrsel_enum, + tegra210_dmic_get_control, tegra210_dmic_put_control), +}; + +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 000000000000..6418c223b1c8 --- /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 000000000000..722092181583 --- /dev/null +++ b/sound/soc/tegra/tegra210_i2s.c @@ -0,0 +1,812 @@ +// 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 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 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_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_get_control(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); + long *uctl_val = &ucontrol->value.integer.value[0]; + + if (strstr(kcontrol->id.name, "Loopback")) + *uctl_val = i2s->loopback; + else if (strstr(kcontrol->id.name, "FSYNC Width")) + *uctl_val = i2s->fsync_width; + else if (strstr(kcontrol->id.name, "Capture Stereo To Mono")) + *uctl_val = i2s->stereo_to_mono[I2S_TX_PATH]; + else if (strstr(kcontrol->id.name, "Capture Mono To Stereo")) + *uctl_val = i2s->mono_to_stereo[I2S_TX_PATH]; + else if (strstr(kcontrol->id.name, "Playback Stereo To Mono")) + *uctl_val = i2s->stereo_to_mono[I2S_RX_PATH]; + else if (strstr(kcontrol->id.name, "Playback Mono To Stereo")) + *uctl_val = i2s->mono_to_stereo[I2S_RX_PATH]; + else if (strstr(kcontrol->id.name, "Playback FIFO Threshold")) + *uctl_val = i2s->rx_fifo_th; + else if (strstr(kcontrol->id.name, "BCLK Ratio")) + *uctl_val = i2s->bclk_ratio; + + return 0; +} + +static int tegra210_i2s_put_control(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 (strstr(kcontrol->id.name, "Loopback")) { + i2s->loopback = value; + + regmap_update_bits(i2s->regmap, TEGRA210_I2S_CTRL, + I2S_CTRL_LPBK_MASK, + i2s->loopback << I2S_CTRL_LPBK_SHIFT); + + } else if (strstr(kcontrol->id.name, "FSYNC Width")) { + /* + * 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. + */ + i2s->fsync_width = value; + + regmap_update_bits(i2s->regmap, TEGRA210_I2S_CTRL, + I2S_CTRL_FSYNC_WIDTH_MASK, + i2s->fsync_width << I2S_FSYNC_WIDTH_SHIFT); + + } else if (strstr(kcontrol->id.name, "Capture Stereo To Mono")) { + i2s->stereo_to_mono[I2S_TX_PATH] = value; + } else if (strstr(kcontrol->id.name, "Capture Mono To Stereo")) { + i2s->mono_to_stereo[I2S_TX_PATH] = value; + } else if (strstr(kcontrol->id.name, "Playback Stereo To Mono")) { + i2s->stereo_to_mono[I2S_RX_PATH] = value; + } else if (strstr(kcontrol->id.name, "Playback Mono To Stereo")) { + i2s->mono_to_stereo[I2S_RX_PATH] = value; + } else if (strstr(kcontrol->id.name, "Playback FIFO Threshold")) { + i2s->rx_fifo_th = value; + } else if (strstr(kcontrol->id.name, "BCLK Ratio")) { + i2s->bclk_ratio = value; + } + + 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_control, + tegra210_i2s_put_control), + SOC_SINGLE_EXT("FSYNC Width", 0, 0, 255, 0, tegra210_i2s_get_control, + tegra210_i2s_put_control), + SOC_ENUM_EXT("Capture Stereo To Mono", tegra210_i2s_stereo_conv_enum, + tegra210_i2s_get_control, tegra210_i2s_put_control), + SOC_ENUM_EXT("Capture Mono To Stereo", tegra210_i2s_mono_conv_enum, + tegra210_i2s_get_control, tegra210_i2s_put_control), + SOC_ENUM_EXT("Playback Stereo To Mono", tegra210_i2s_stereo_conv_enum, + tegra210_i2s_get_control, tegra210_i2s_put_control), + SOC_ENUM_EXT("Playback Mono To Stereo", tegra210_i2s_mono_conv_enum, + tegra210_i2s_get_control, tegra210_i2s_put_control), + SOC_SINGLE_EXT("Playback FIFO Threshold", 0, 0, I2S_RX_FIFO_DEPTH - 1, + 0, tegra210_i2s_get_control, tegra210_i2s_put_control), + SOC_SINGLE_EXT("BCLK Ratio", 0, 0, INT_MAX, 0, tegra210_i2s_get_control, + tegra210_i2s_put_control), +}; + +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 000000000000..030d70c45e18 --- /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 index 635eacbd28d4..156e3b9d613c 100644 --- a/sound/soc/tegra/tegra30_ahub.c +++ b/sound/soc/tegra/tegra30_ahub.c @@ -643,8 +643,10 @@ static int tegra30_ahub_resume(struct device *dev) int ret; ret = pm_runtime_get_sync(dev); - if (ret < 0) + 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); diff --git a/sound/soc/tegra/tegra30_i2s.c b/sound/soc/tegra/tegra30_i2s.c index d59882ec48f1..db5a8587bfa4 100644 --- a/sound/soc/tegra/tegra30_i2s.c +++ b/sound/soc/tegra/tegra30_i2s.c @@ -567,8 +567,10 @@ static int tegra30_i2s_resume(struct device *dev) int ret; ret = pm_runtime_get_sync(dev); - if (ret < 0) + if (ret < 0) { + pm_runtime_put(dev); return ret; + } ret = regcache_sync(i2s->regmap); pm_runtime_put(dev); diff --git a/sound/soc/tegra/tegra_alc5632.c b/sound/soc/tegra/tegra_alc5632.c index 2839c6cb8c38..8661877bf4c6 100644 --- a/sound/soc/tegra/tegra_alc5632.c +++ b/sound/soc/tegra/tegra_alc5632.c @@ -36,7 +36,7 @@ struct tegra_alc5632 { static int tegra_alc5632_asoc_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_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); diff --git a/sound/soc/tegra/tegra_cif.h b/sound/soc/tegra/tegra_cif.h new file mode 100644 index 000000000000..7cca8068f4b5 --- /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 index ec9050516cd7..af3e9e6daa40 100644 --- a/sound/soc/tegra/tegra_max98090.c +++ b/sound/soc/tegra/tegra_max98090.c @@ -37,7 +37,7 @@ struct tegra_max98090 { static int tegra_max98090_asoc_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_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); diff --git a/sound/soc/tegra/tegra_pcm.c b/sound/soc/tegra/tegra_pcm.c index f246df8ecf7b..b3f36515cbc1 100644 --- a/sound/soc/tegra/tegra_pcm.c +++ b/sound/soc/tegra/tegra_pcm.c @@ -16,12 +16,12 @@ */ #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 = { @@ -67,6 +67,239 @@ void tegra_pcm_platform_unregister(struct device *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 index 0433372e68d4..4838cdcee20e 100644 --- a/sound/soc/tegra/tegra_pcm.h +++ b/sound/soc/tegra/tegra_pcm.h @@ -17,8 +17,27 @@ #ifndef __TEGRA_PCM_H__ #define __TEGRA_PCM_H__ -struct snd_dmaengine_pcm_config; +#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, diff --git a/sound/soc/tegra/tegra_rt5640.c b/sound/soc/tegra/tegra_rt5640.c index 201d132731f9..d66d8659396b 100644 --- a/sound/soc/tegra/tegra_rt5640.c +++ b/sound/soc/tegra/tegra_rt5640.c @@ -39,7 +39,7 @@ struct tegra_rt5640 { static int tegra_rt5640_asoc_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_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); diff --git a/sound/soc/tegra/tegra_rt5677.c b/sound/soc/tegra/tegra_rt5677.c index 8f71e21f6ee9..7504507dd8b8 100644 --- a/sound/soc/tegra/tegra_rt5677.c +++ b/sound/soc/tegra/tegra_rt5677.c @@ -41,7 +41,7 @@ struct tegra_rt5677 { static int tegra_rt5677_asoc_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_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); diff --git a/sound/soc/tegra/tegra_sgtl5000.c b/sound/soc/tegra/tegra_sgtl5000.c index 692fcc3d7d6e..e1dc8e7d337a 100644 --- a/sound/soc/tegra/tegra_sgtl5000.c +++ b/sound/soc/tegra/tegra_sgtl5000.c @@ -35,7 +35,7 @@ struct tegra_sgtl5000 { static int tegra_sgtl5000_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_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); diff --git a/sound/soc/tegra/tegra_wm8753.c b/sound/soc/tegra/tegra_wm8753.c index 2ee2ed190872..ec3ee0580867 100644 --- a/sound/soc/tegra/tegra_wm8753.c +++ b/sound/soc/tegra/tegra_wm8753.c @@ -39,7 +39,7 @@ struct tegra_wm8753 { static int tegra_wm8753_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_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); diff --git a/sound/soc/tegra/tegra_wm8903.c b/sound/soc/tegra/tegra_wm8903.c index d3ead0213cef..ef6652aaac9b 100644 --- a/sound/soc/tegra/tegra_wm8903.c +++ b/sound/soc/tegra/tegra_wm8903.c @@ -44,7 +44,7 @@ struct tegra_wm8903 { static int tegra_wm8903_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_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); diff --git a/sound/soc/tegra/trimslice.c b/sound/soc/tegra/trimslice.c index 6dca6836aa04..cdb386d6e5c3 100644 --- a/sound/soc/tegra/trimslice.c +++ b/sound/soc/tegra/trimslice.c @@ -34,7 +34,7 @@ struct tegra_trimslice { static int trimslice_asoc_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_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); |