Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+318
View File
@@ -0,0 +1,318 @@
# SPDX-License-Identifier: GPL-2.0-only
menu "Tegra"
config SND_SOC_TEGRA
tristate "SoC Audio for the Tegra System-on-Chip"
depends on ARCH_TEGRA || COMPILE_TEST
depends on COMMON_CLK
depends on RESET_CONTROLLER
select REGMAP_MMIO
select SND_SOC_GENERIC_DMAENGINE_PCM
help
Say Y or M here if you want support for SoC audio on Tegra.
if SND_SOC_TEGRA
config SND_SOC_TEGRA20_AC97
tristate "Tegra20 AC97 interface"
select SND_SOC_AC97_BUS
select SND_SOC_TEGRA20_DAS
help
Say Y or M if you want to add support for codecs attached to the
Tegra20 AC97 interface. You will also need to select the individual
machine drivers to support below.
config SND_SOC_TEGRA20_DAS
tristate "Tegra20 DAS module"
help
Say Y or M if you want to add support for the Tegra20 DAS module.
You will also need to select the individual machine drivers to
support below.
config SND_SOC_TEGRA20_I2S
tristate "Tegra20 I2S interface"
select SND_SOC_TEGRA20_DAS
help
Say Y or M if you want to add support for codecs attached to the
Tegra20 I2S interface. You will also need to select the individual
machine drivers to support below.
config SND_SOC_TEGRA20_SPDIF
tristate "Tegra20 SPDIF interface"
help
Say Y or M if you want to add support for the Tegra20 SPDIF interface.
You will also need to select the individual machine drivers to support
below.
config SND_SOC_TEGRA30_AHUB
tristate "Tegra30 AHUB module"
help
Say Y or M if you want to add support for the Tegra30 AHUB module.
You will also need to select the individual machine drivers to
support below.
config SND_SOC_TEGRA30_I2S
tristate "Tegra30 I2S interface"
select SND_SOC_TEGRA30_AHUB
help
Say Y or M if you want to add support for codecs attached to the
Tegra30 I2S interface. You will also need to select the individual
machine drivers to support below.
config SND_SOC_TEGRA210_AHUB
tristate "Tegra210 AHUB module"
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"
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"
select SND_SIMPLE_CARD_UTILS
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_TEGRA210_OPE
tristate "Tegra210 OPE module"
help
Config to enable the Output Processing Engine (OPE) which includes
Parametric Equalizer (PEQ) and Multi Band Dynamic Range Compressor
(MBDRC) sub blocks for data processing. It can support up to 8
channels.
Say Y or M if you want to add support for Tegra210 OPE module.
config SND_SOC_TEGRA186_ASRC
tristate "Tegra186 ASRC module"
help
Config to enable the Asynchronous Sample Rate Converter (ASRC),
which converts the sampling frequency of the input signal from
one frequency to another. It can handle over a wide range of
sample rate ratios (freq_in/freq_out) from 1:24 to 24:1.
ASRC has two modes of operation. One where ratio can be programmed
in SW and the other where it gets information from ratio estimator
module.
Say Y or M if you want to add support for Tegra186 ASRC module.
config SND_SOC_TEGRA186_DSPK
tristate "Tegra186 DSPK module"
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"
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_TEGRA210_MVC
tristate "Tegra210 MVC module"
help
Config to enable the digital Master Volume Controller (MVC) which
provides gain or attenuation to a digital signal path. It can be
used in input or output signal path. It can be used either for
per-stream volume control or for master volume control.
Say Y or M if you want to add support for Tegra210 MVC module.
config SND_SOC_TEGRA210_SFC
tristate "Tegra210 SFC module"
help
Config to enable the Sampling Frequency Converter (SFC) which
converts the sampling frequency of input signal to another
frequency. It supports sampling frequency conversion of streams
up to 2 channels (stereo).
Say Y or M if you want to add support for Tegra210 SFC module.
config SND_SOC_TEGRA210_AMX
tristate "Tegra210 AMX module"
help
Config to enable the Audio Multiplexer (AMX) which can multiplex
four input streams (each of up to 16 channels) and generate
output stream (of up to 16 channels). A byte RAM helps to form an
output frame by any combination of bytes from the input frames.
Say Y or M if you want to add support for Tegra210 AMX module.
config SND_SOC_TEGRA210_ADX
tristate "Tegra210 ADX module"
help
Config to enable the Audio Demultiplexer (ADX) which takes an
input stream (up to 16 channels) and demultiplexes it into four
output streams (each of up to 16 channels). A byte RAM helps to
form output frames by any combination of bytes from the input
frame. Its design is identical to that of byte RAM in the AMX
except that the data flow direction is reversed.
Say Y or M if you want to add support for Tegra210 ADX module.
config SND_SOC_TEGRA210_MIXER
tristate "Tegra210 Mixer module"
help
Config to enable the Mixer module which can help to mix multiple
audio streams. It supports mixing of up to 10 input streams,
where each stream can contain maximum of 8 channels. It supports
5 output each of which can be a mix of any combination of 10
input streams.
Say Y or M if you want to add support for Tegra210 Mixer module.
config SND_SOC_TEGRA_AUDIO_GRAPH_CARD
tristate "Audio Graph Card based Tegra driver"
depends on SND_AUDIO_GRAPH_CARD
help
Config to enable Tegra audio machine driver based on generic
audio graph driver. It is a thin driver written to customize
few things for Tegra audio. Most of the code is re-used from
audio graph driver and the same DT bindings are used.
config SND_SOC_TEGRA_MACHINE_DRV
tristate
config SND_SOC_TEGRA_RT5631
tristate "SoC Audio support for Tegra boards using an RT5631 codec"
depends on SND_SOC_TEGRA && I2C && GPIOLIB
select SND_SOC_TEGRA_MACHINE_DRV
select SND_SOC_RT5631
help
Say Y or M here if you want to add support for SoC audio on Tegra
boards using the RT5631 codec, such as Transformer.
config SND_SOC_TEGRA_RT5640
tristate "SoC Audio support for Tegra boards using an RT5640 codec"
depends on I2C && GPIOLIB
select SND_SOC_TEGRA_MACHINE_DRV
select SND_SOC_RT5640
help
Say Y or M here if you want to add support for SoC audio on Tegra
boards using the RT5640 codec, such as Dalmore.
config SND_SOC_TEGRA_WM8753
tristate "SoC Audio support for Tegra boards using a WM8753 codec"
depends on I2C && GPIOLIB
select SND_SOC_TEGRA_MACHINE_DRV
select SND_SOC_WM8753
help
Say Y or M here if you want to add support for SoC audio on Tegra
boards using the WM8753 codec, such as Whistler.
config SND_SOC_TEGRA_WM8903
tristate "SoC Audio support for Tegra boards using a WM8903 codec"
depends on I2C && GPIOLIB
select SND_SOC_TEGRA_MACHINE_DRV
select SND_SOC_WM8903
help
Say Y or M here if you want to add support for SoC audio on Tegra
boards using the WM8093 codec. Currently, the supported boards are
Harmony, Ventana, Seaboard, Kaen, and Aebl.
config SND_SOC_TEGRA_WM8962
tristate "SoC Audio support for Tegra boards using a WM8962 codec"
depends on I2C && INPUT && GPIOLIB
select SND_SOC_TEGRA_MACHINE_DRV
select SND_SOC_WM8962
help
Say Y or M here if you want to add support for SoC audio on Tegra
boards using the WM8962 codec. Currently, the supported boards are
Microsoft Surface RT.
config SND_SOC_TEGRA_WM9712
tristate "SoC Audio support for Tegra boards using a WM9712 codec"
depends on GPIOLIB
select SND_SOC_TEGRA_MACHINE_DRV
select SND_SOC_TEGRA20_AC97
select SND_SOC_WM9712
help
Say Y or M here if you want to add support for SoC audio on Tegra
boards using the WM9712 (or compatible) codec.
config SND_SOC_TEGRA_TRIMSLICE
tristate "SoC Audio support for TrimSlice board"
depends on I2C
select SND_SOC_TEGRA_MACHINE_DRV
select SND_SOC_TLV320AIC23_I2C
help
Say Y or M here if you want to add support for SoC audio on the
TrimSlice platform.
config SND_SOC_TEGRA_ALC5632
tristate "SoC Audio support for Tegra boards using an ALC5632 codec"
depends on I2C && GPIOLIB
select SND_SOC_TEGRA_MACHINE_DRV
select SND_SOC_ALC5632
help
Say Y or M here if you want to add support for SoC audio on the
Toshiba AC100 netbook.
config SND_SOC_TEGRA_MAX98090
tristate "SoC Audio support for Tegra boards using a MAX98090 codec"
depends on I2C && GPIOLIB
select SND_SOC_TEGRA_MACHINE_DRV
select SND_SOC_MAX98090
help
Say Y or M here if you want to add support for SoC audio on Tegra
boards using the MAX98090 codec, such as Venice2.
config SND_SOC_TEGRA_MAX98088
tristate "SoC Audio support for Tegra boards using a MAX9808x codec"
depends on I2C && GPIOLIB
select SND_SOC_TEGRA_MACHINE_DRV
select SND_SOC_MAX98088
help
Say Y or M here if you want to add support for SoC audio on Tegra
boards using the MAX98088 codec, such as LG X3.
config SND_SOC_TEGRA_RT5677
tristate "SoC Audio support for Tegra boards using a RT5677 codec"
depends on I2C && GPIOLIB
select SND_SOC_TEGRA_MACHINE_DRV
select SND_SOC_RT5677
help
Say Y or M here if you want to add support for SoC audio on Tegra
boards using the RT5677 codec, such as Ryu.
config SND_SOC_TEGRA_SGTL5000
tristate "SoC Audio support for Tegra boards using a SGTL5000 codec"
depends on I2C && GPIOLIB
select SND_SOC_TEGRA_MACHINE_DRV
select SND_SOC_SGTL5000
help
Say Y or M here if you want to add support for SoC audio on Tegra
boards using the SGTL5000 codec, such as Apalis T30, Apalis TK1 or
Colibri T30.
config SND_SOC_TEGRA_CPCAP
tristate "SoC Audio support for Tegra boards using a CPCAP codec"
depends on I2C && GPIOLIB && MFD_CPCAP
select SND_SOC_TEGRA_MACHINE_DRV
select SND_SOC_CPCAP
help
Say Y or M here if you want to add support for SoC audio on Tegra
boards using the CPCAP codec, such as Motorola Atrix 4G or Droid X2.
endif
endmenu
+52
View File
@@ -0,0 +1,52 @@
# SPDX-License-Identifier: GPL-2.0
# Tegra platform Support
snd-soc-tegra-pcm-y := tegra_pcm.o
snd-soc-tegra20-ac97-y := tegra20_ac97.o
snd-soc-tegra20-das-y := tegra20_das.o
snd-soc-tegra20-i2s-y := tegra20_i2s.o
snd-soc-tegra20-spdif-y := tegra20_spdif.o
snd-soc-tegra30-ahub-y := tegra30_ahub.o
snd-soc-tegra30-i2s-y := tegra30_i2s.o
snd-soc-tegra210-ahub-y := tegra210_ahub.o
snd-soc-tegra210-dmic-y := tegra210_dmic.o
snd-soc-tegra210-i2s-y := tegra210_i2s.o
snd-soc-tegra186-asrc-y := tegra186_asrc.o
snd-soc-tegra186-dspk-y := tegra186_dspk.o
snd-soc-tegra210-admaif-y := tegra210_admaif.o tegra_isomgr_bw.o
snd-soc-tegra210-mvc-y := tegra210_mvc.o
snd-soc-tegra210-sfc-y := tegra210_sfc.o
snd-soc-tegra210-amx-y := tegra210_amx.o
snd-soc-tegra210-adx-y := tegra210_adx.o
snd-soc-tegra210-mixer-y := tegra210_mixer.o
snd-soc-tegra210-ope-y := tegra210_ope.o tegra210_mbdrc.o tegra210_peq.o
obj-$(CONFIG_SND_SOC_TEGRA) += snd-soc-tegra-pcm.o
obj-$(CONFIG_SND_SOC_TEGRA20_AC97) += snd-soc-tegra20-ac97.o
obj-$(CONFIG_SND_SOC_TEGRA20_DAS) += snd-soc-tegra20-das.o
obj-$(CONFIG_SND_SOC_TEGRA20_I2S) += snd-soc-tegra20-i2s.o
obj-$(CONFIG_SND_SOC_TEGRA20_SPDIF) += snd-soc-tegra20-spdif.o
obj-$(CONFIG_SND_SOC_TEGRA30_AHUB) += snd-soc-tegra30-ahub.o
obj-$(CONFIG_SND_SOC_TEGRA30_I2S) += snd-soc-tegra30-i2s.o
obj-$(CONFIG_SND_SOC_TEGRA210_DMIC) += snd-soc-tegra210-dmic.o
obj-$(CONFIG_SND_SOC_TEGRA210_AHUB) += snd-soc-tegra210-ahub.o
obj-$(CONFIG_SND_SOC_TEGRA210_I2S) += snd-soc-tegra210-i2s.o
obj-$(CONFIG_SND_SOC_TEGRA186_ASRC) += snd-soc-tegra186-asrc.o
obj-$(CONFIG_SND_SOC_TEGRA186_DSPK) += snd-soc-tegra186-dspk.o
obj-$(CONFIG_SND_SOC_TEGRA210_ADMAIF) += snd-soc-tegra210-admaif.o
obj-$(CONFIG_SND_SOC_TEGRA210_MVC) += snd-soc-tegra210-mvc.o
obj-$(CONFIG_SND_SOC_TEGRA210_SFC) += snd-soc-tegra210-sfc.o
obj-$(CONFIG_SND_SOC_TEGRA210_AMX) += snd-soc-tegra210-amx.o
obj-$(CONFIG_SND_SOC_TEGRA210_ADX) += snd-soc-tegra210-adx.o
obj-$(CONFIG_SND_SOC_TEGRA210_MIXER) += snd-soc-tegra210-mixer.o
obj-$(CONFIG_SND_SOC_TEGRA210_OPE) += snd-soc-tegra210-ope.o
# Tegra machine Support
snd-soc-tegra-wm8903-y := tegra_wm8903.o
snd-soc-tegra-wm8962-y := tegra_wm8962.o
snd-soc-tegra-machine-y := tegra_asoc_machine.o
snd-soc-tegra-audio-graph-card-y := tegra_audio_graph_card.o
obj-$(CONFIG_SND_SOC_TEGRA_WM8903) += snd-soc-tegra-wm8903.o
obj-$(CONFIG_SND_SOC_TEGRA_WM8962) += snd-soc-tegra-wm8962.o
obj-$(CONFIG_SND_SOC_TEGRA_MACHINE_DRV) += snd-soc-tegra-machine.o
obj-$(CONFIG_SND_SOC_TEGRA_AUDIO_GRAPH_CARD) += snd-soc-tegra-audio-graph-card.o
File diff suppressed because it is too large Load Diff
+116
View File
@@ -0,0 +1,116 @@
/* SPDX-License-Identifier: GPL-2.0-only
* SPDX-FileCopyrightText: Copyright (c) 2022-2024 NVIDIA CORPORATION. All rights reserved.
* tegra186_asrc.h - Definitions for Tegra186 ASRC driver
*
*/
#ifndef __TEGRA186_ASRC_H__
#define __TEGRA186_ASRC_H__
/* ASRC stream related offset */
#define TEGRA186_ASRC_CFG 0x0
#define TEGRA186_ASRC_RATIO_INT_PART 0x4
#define TEGRA186_ASRC_RATIO_FRAC_PART 0x8
#define TEGRA186_ASRC_RATIO_LOCK_STATUS 0xc
#define TEGRA186_ASRC_MUTE_UNMUTE_DURATION 0x10
#define TEGRA186_ASRC_TX_THRESHOLD 0x14
#define TEGRA186_ASRC_RX_THRESHOLD 0x18
#define TEGRA186_ASRC_RATIO_COMP 0x1c
#define TEGRA186_ASRC_RX_STATUS 0x20
#define TEGRA186_ASRC_RX_CIF_CTRL 0x24
#define TEGRA186_ASRC_TX_STATUS 0x2c
#define TEGRA186_ASRC_TX_CIF_CTRL 0x30
#define TEGRA186_ASRC_ENABLE 0x38
#define TEGRA186_ASRC_SOFT_RESET 0x3c
#define TEGRA186_ASRC_STATUS 0x4c
#define TEGRA186_ASRC_STATEBUF_ADDR 0x5c
#define TEGRA186_ASRC_STATEBUF_CFG 0x60
#define TEGRA186_ASRC_INSAMPLEBUF_ADDR 0x64
#define TEGRA186_ASRC_INSAMPLEBUF_CFG 0x68
#define TEGRA186_ASRC_OUTSAMPLEBUF_ADDR 0x6c
#define TEGRA186_ASRC_OUTSAMPLEBUF_CFG 0x70
/* ASRC Global registers offset */
#define TEGRA186_ASRC_GLOBAL_ENB 0x2f4
#define TEGRA186_ASRC_GLOBAL_SOFT_RESET 0x2f8
#define TEGRA186_ASRC_GLOBAL_CG 0x2fc
#define TEGRA186_ASRC_GLOBAL_CFG 0x300
#define TEGRA186_ASRC_GLOBAL_SCRATCH_ADDR 0x304
#define TEGRA186_ASRC_GLOBAL_SCRATCH_CFG 0x308
#define TEGRA186_ASRC_RATIO_UPD_RX_CIF_CTRL 0x30c
#define TEGRA186_ASRC_RATIO_UPD_RX_STATUS 0x310
#define TEGRA186_ASRC_GLOBAL_STATUS 0x314
#define TEGRA186_ASRC_GLOBAL_STREAM_ENABLE_STATUS 0x318
#define TEGRA186_ASRC_GLOBAL_INT_STATUS 0x324
#define TEGRA186_ASRC_GLOBAL_INT_MASK 0x328
#define TEGRA186_ASRC_GLOBAL_INT_SET 0x32c
#define TEGRA186_ASRC_GLOBAL_INT_CLEAR 0x330
#define TEGRA186_ASRC_GLOBAL_TRANSFER_ERROR_LOG 0x334
#define TEGRA186_ASRC_GLOBAL_APR_CTRL 0x1000
#define TEGRA186_ASRC_GLOBAL_APR_CTRL_ACCESS_CTRL 0x1004
#define TEGRA186_ASRC_GLOBAL_DISARM_APR 0x1008
#define TEGRA186_ASRC_GLOBAL_DISARM_APR_ACCESS_CTRL 0x100c
#define TEGRA186_ASRC_GLOBAL_RATIO_WR_ACCESS 0x1010
#define TEGRA186_ASRC_GLOBAL_RATIO_WR_ACCESS_CTRL 0x1014
#define TEGRA186_ASRC_CYA 0x1018
#define TEGRA186_ASRC_STREAM_DEFAULT_HW_COMP_BIAS_VALUE 0xaaaa
#define TEGRA186_ASRC_STREAM_DEFAULT_INPUT_HW_COMP_THRESH_CFG 0x00201002
#define TEGRA186_ASRC_STREAM_DEFAULT_OUTPUT_HW_COMP_THRESH_CFG 0x00201002
#define TEGRA186_ASRC_GLOBAL_CFG_FRAC_28BIT_PRECISION 0
#define TEGRA186_ASRC_GLOBAL_CFG_FRAC_32BIT_PRECISION 1
#define TEGRA186_ASRC_STREAM_ENABLE_HW_RATIO_COMP_SHIFT 31
#define TEGRA186_ASRC_STREAM_ENABLE_HW_RATIO_COMP_MASK (1 << TEGRA186_ASRC_STREAM_ENABLE_HW_RATIO_COMP_SHIFT)
#define TEGRA186_ASRC_STREAM_ENABLE_HW_RATIO_COMP_ENABLE (1 << TEGRA186_ASRC_STREAM_ENABLE_HW_RATIO_COMP_SHIFT)
#define TEGRA186_ASRC_STREAM_ENABLE_HW_RATIO_COMP_DISABLE (0 << TEGRA186_ASRC_STREAM_ENABLE_HW_RATIO_COMP_SHIFT)
#define TEGRA186_ASRC_STREAM_RATIO_TYPE_SHIFT 0
#define TEGRA186_ASRC_STREAM_RATIO_TYPE_MASK (1 << TEGRA186_ASRC_STREAM_RATIO_TYPE_SHIFT)
#define TEGRA186_ASRC_STREAM_EN_SHIFT 0
#define TEGRA186_ASRC_STREAM_EN (1 << TEGRA186_ASRC_STREAM_EN_SHIFT)
#define TEGRA186_ASRC_GLOBAL_EN_SHIFT 0
#define TEGRA186_ASRC_GLOBAL_EN (1 << TEGRA186_ASRC_GLOBAL_EN_SHIFT)
#define TEGRA186_ASRC_STREAM_STATEBUF_CFG_SIZE_SHIFT 0
#define TEGRA186_ASRC_STREAM_STATEBUF_CFG_SIZE_MASK (0xffff << TEGRA186_ASRC_STREAM_STATEBUF_CFG_SIZE_SHIFT)
#define TEGRA186_ASRC_STREAM_INSAMPLEBUF_CFG_SIZE_SHIFT 0
#define TEGRA186_ASRC_STREAM_INSAMPLEBUF_CFG_SIZE_MASK (0xffff << TEGRA186_ASRC_STREAM_INSAMPLEBUF_CFG_SIZE_SHIFT)
#define TEGRA186_ASRC_STREAM_OUTSAMPLEBUF_CFG_SIZE_SHIFT 0
#define TEGRA186_ASRC_STREAM_OUTSAMPLEBUF_CFG_SIZE_MASK (0xffff << TEGRA186_ASRC_STREAM_OUTSAMPLEBUF_CFG_SIZE_SHIFT)
#define TEGRA186_ASRC_STREAM_RATIO_INT_PART_MASK 0x1f
#define TEGRA186_ASRC_STREAM_RATIO_FRAC_PART_MASK 0xffffffff
#define TEGRA186_ASRC_STREAM_STRIDE 0x80
#define TEGRA186_ASRC_STREAM_MAX 0x6
#define TEGRA186_ASRC_STREAM_LIMIT 0x2f0
#define TEGRA186_ASRC_RATIO_SOURCE_ARAD 0x0
#define TEGRA186_ASRC_RATIO_SOURCE_SW 0x1
#define TEGRA186_ASRC_ARAM_START_ADDR 0x3f800000
#define TEGRA264_ASRC_ARAM_START_ADDR 0x8a080000
struct tegra186_asrc_lane {
unsigned int int_part;
unsigned int frac_part;
unsigned int ratio_source;
unsigned int hwcomp_disable;
unsigned int input_thresh;
unsigned int output_thresh;
};
struct tegra_asrc_soc_data {
unsigned int aram_start_addr;
};
struct tegra186_asrc {
const struct tegra_asrc_soc_data *soc_data;
struct tegra186_asrc_lane lane[TEGRA186_ASRC_STREAM_MAX];
struct regmap *regmap;
};
#endif
+550
View File
@@ -0,0 +1,550 @@
// SPDX-License-Identifier: GPL-2.0-only
// SPDX-FileCopyrightText: Copyright (c) 2020-2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
//
// tegra186_dspk.c - Tegra186 DSPK driver
#include <linux/clk.h>
#include <linux/device.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include <sound/core.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include "tegra186_dspk.h"
#include "tegra_cif.h"
static const struct reg_default tegra186_dspk_reg_defaults[] = {
{ TEGRA186_DSPK_RX_INT_MASK, 0x00000007 },
{ TEGRA186_DSPK_RX_CIF_CTRL, 0x00007700 },
{ TEGRA186_DSPK_CG, 0x00000001 },
{ TEGRA186_DSPK_CORE_CTRL, 0x00000310 },
{ TEGRA186_DSPK_CODEC_CTRL, 0x03000000 },
};
static int tegra186_dspk_get_fifo_th(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *codec = snd_kcontrol_chip(kcontrol);
struct tegra186_dspk *dspk = snd_soc_component_get_drvdata(codec);
ucontrol->value.integer.value[0] = dspk->rx_fifo_th;
return 0;
}
static int tegra186_dspk_put_fifo_th(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *codec = snd_kcontrol_chip(kcontrol);
struct tegra186_dspk *dspk = snd_soc_component_get_drvdata(codec);
int value = ucontrol->value.integer.value[0];
if (value == dspk->rx_fifo_th)
return 0;
dspk->rx_fifo_th = value;
return 1;
}
static int tegra186_dspk_get_osr_val(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *codec = snd_kcontrol_chip(kcontrol);
struct tegra186_dspk *dspk = snd_soc_component_get_drvdata(codec);
ucontrol->value.enumerated.item[0] = dspk->osr_val;
return 0;
}
static int tegra186_dspk_put_osr_val(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *codec = snd_kcontrol_chip(kcontrol);
struct tegra186_dspk *dspk = snd_soc_component_get_drvdata(codec);
unsigned int value = ucontrol->value.enumerated.item[0];
if (value == dspk->osr_val)
return 0;
dspk->osr_val = value;
return 1;
}
static int tegra186_dspk_get_pol_sel(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *codec = snd_kcontrol_chip(kcontrol);
struct tegra186_dspk *dspk = snd_soc_component_get_drvdata(codec);
ucontrol->value.enumerated.item[0] = dspk->lrsel;
return 0;
}
static int tegra186_dspk_put_pol_sel(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *codec = snd_kcontrol_chip(kcontrol);
struct tegra186_dspk *dspk = snd_soc_component_get_drvdata(codec);
unsigned int value = ucontrol->value.enumerated.item[0];
if (value == dspk->lrsel)
return 0;
dspk->lrsel = value;
return 1;
}
static int tegra186_dspk_get_ch_sel(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *codec = snd_kcontrol_chip(kcontrol);
struct tegra186_dspk *dspk = snd_soc_component_get_drvdata(codec);
ucontrol->value.enumerated.item[0] = dspk->ch_sel;
return 0;
}
static int tegra186_dspk_put_ch_sel(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *codec = snd_kcontrol_chip(kcontrol);
struct tegra186_dspk *dspk = snd_soc_component_get_drvdata(codec);
unsigned int value = ucontrol->value.enumerated.item[0];
if (value == dspk->ch_sel)
return 0;
dspk->ch_sel = value;
return 1;
}
static int tegra186_dspk_get_mono_to_stereo(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *codec = snd_kcontrol_chip(kcontrol);
struct tegra186_dspk *dspk = snd_soc_component_get_drvdata(codec);
ucontrol->value.enumerated.item[0] = dspk->mono_to_stereo;
return 0;
}
static int tegra186_dspk_put_mono_to_stereo(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *codec = snd_kcontrol_chip(kcontrol);
struct tegra186_dspk *dspk = snd_soc_component_get_drvdata(codec);
unsigned int value = ucontrol->value.enumerated.item[0];
if (value == dspk->mono_to_stereo)
return 0;
dspk->mono_to_stereo = value;
return 1;
}
static int tegra186_dspk_get_stereo_to_mono(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *codec = snd_kcontrol_chip(kcontrol);
struct tegra186_dspk *dspk = snd_soc_component_get_drvdata(codec);
ucontrol->value.enumerated.item[0] = dspk->stereo_to_mono;
return 0;
}
static int tegra186_dspk_put_stereo_to_mono(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *codec = snd_kcontrol_chip(kcontrol);
struct tegra186_dspk *dspk = snd_soc_component_get_drvdata(codec);
unsigned int value = ucontrol->value.enumerated.item[0];
if (value == dspk->stereo_to_mono)
return 0;
dspk->stereo_to_mono = value;
return 1;
}
static int 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;
}
switch (params_format(params)) {
case SNDRV_PCM_FORMAT_S16_LE:
cif_conf.audio_bits = TEGRA_ACIF_BITS_16;
cif_conf.client_bits = TEGRA_ACIF_BITS_16;
break;
case SNDRV_PCM_FORMAT_S24_LE:
case SNDRV_PCM_FORMAT_S32_LE:
cif_conf.audio_bits = TEGRA_ACIF_BITS_32;
cif_conf.client_bits = TEGRA_ACIF_BITS_24;
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_S24_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_S24_LE |
SNDRV_PCM_FMTBIT_S32_LE,
},
.ops = &tegra186_dspk_dai_ops,
.symmetric_rate = 1,
},
};
static const struct snd_soc_dapm_widget tegra186_dspk_widgets[] = {
SND_SOC_DAPM_AIF_IN("RX", NULL, 0, TEGRA186_DSPK_ENABLE, 0, 0),
SND_SOC_DAPM_SPK("SPK", NULL),
};
static const struct snd_soc_dapm_route tegra186_dspk_routes[] = {
{ "XBAR-Playback", NULL, "XBAR-TX" },
{ "CIF-Playback", NULL, "XBAR-Playback" },
{ "RX", NULL, "CIF-Playback" },
{ "DAP-Playback", NULL, "RX" },
{ "SPK", NULL, "DAP-Playback" },
};
static const char * const tegra186_dspk_ch_sel_text[] = {
"Left", "Right", "Stereo",
};
static const struct soc_enum tegra186_dspk_ch_sel_enum =
SOC_ENUM_SINGLE(SND_SOC_NOPM, 0, ARRAY_SIZE(tegra186_dspk_ch_sel_text),
tegra186_dspk_ch_sel_text);
static const char * const tegra186_dspk_osr_text[] = {
"OSR_32", "OSR_64", "OSR_128", "OSR_256",
};
static const struct soc_enum tegra186_dspk_osr_enum =
SOC_ENUM_SINGLE(SND_SOC_NOPM, 0, ARRAY_SIZE(tegra186_dspk_osr_text),
tegra186_dspk_osr_text);
static const char * const tegra186_dspk_lrsel_text[] = {
"Left", "Right",
};
static const char * const tegra186_dspk_mono_conv_text[] = {
"Zero", "Copy",
};
static const struct soc_enum tegra186_dspk_mono_conv_enum =
SOC_ENUM_SINGLE(SND_SOC_NOPM, 0,
ARRAY_SIZE(tegra186_dspk_mono_conv_text),
tegra186_dspk_mono_conv_text);
static const char * const tegra186_dspk_stereo_conv_text[] = {
"CH0", "CH1", "AVG",
};
static const struct soc_enum tegra186_dspk_stereo_conv_enum =
SOC_ENUM_SINGLE(SND_SOC_NOPM, 0,
ARRAY_SIZE(tegra186_dspk_stereo_conv_text),
tegra186_dspk_stereo_conv_text);
static const struct soc_enum tegra186_dspk_lrsel_enum =
SOC_ENUM_SINGLE(SND_SOC_NOPM, 0, ARRAY_SIZE(tegra186_dspk_lrsel_text),
tegra186_dspk_lrsel_text);
static const struct snd_kcontrol_new tegrat186_dspk_controls[] = {
SOC_SINGLE_EXT("FIFO Threshold", SND_SOC_NOPM, 0,
TEGRA186_DSPK_RX_FIFO_DEPTH - 1, 0,
tegra186_dspk_get_fifo_th, tegra186_dspk_put_fifo_th),
SOC_ENUM_EXT("OSR Value", tegra186_dspk_osr_enum,
tegra186_dspk_get_osr_val, tegra186_dspk_put_osr_val),
SOC_ENUM_EXT("LR Polarity Select", tegra186_dspk_lrsel_enum,
tegra186_dspk_get_pol_sel, tegra186_dspk_put_pol_sel),
SOC_ENUM_EXT("Channel Select", tegra186_dspk_ch_sel_enum,
tegra186_dspk_get_ch_sel, tegra186_dspk_put_ch_sel),
SOC_ENUM_EXT("Mono To Stereo", tegra186_dspk_mono_conv_enum,
tegra186_dspk_get_mono_to_stereo,
tegra186_dspk_put_mono_to_stereo),
SOC_ENUM_EXT("Stereo To Mono", tegra186_dspk_stereo_conv_enum,
tegra186_dspk_get_stereo_to_mono,
tegra186_dspk_put_stereo_to_mono),
};
static const struct snd_soc_component_driver tegra186_dspk_cmpnt = {
.dapm_widgets = tegra186_dspk_widgets,
.num_dapm_widgets = ARRAY_SIZE(tegra186_dspk_widgets),
.dapm_routes = tegra186_dspk_routes,
.num_dapm_routes = ARRAY_SIZE(tegra186_dspk_routes),
.controls = tegrat186_dspk_controls,
.num_controls = ARRAY_SIZE(tegrat186_dspk_controls),
};
static bool tegra186_dspk_wr_reg(struct device *dev, unsigned int reg)
{
switch (reg) {
case TEGRA186_DSPK_RX_INT_MASK ... TEGRA186_DSPK_RX_CIF_CTRL:
case TEGRA186_DSPK_ENABLE ... TEGRA186_DSPK_CG:
case TEGRA186_DSPK_CORE_CTRL ... TEGRA186_DSPK_CODEC_CTRL:
return true;
default:
return false;
}
}
static bool tegra186_dspk_rd_reg(struct device *dev, unsigned int reg)
{
if (tegra186_dspk_wr_reg(dev, reg))
return true;
switch (reg) {
case TEGRA186_DSPK_RX_STATUS:
case TEGRA186_DSPK_RX_INT_STATUS:
case TEGRA186_DSPK_STATUS:
case TEGRA186_DSPK_INT_STATUS:
return true;
default:
return false;
}
}
static bool tegra186_dspk_volatile_reg(struct device *dev, unsigned int reg)
{
switch (reg) {
case TEGRA186_DSPK_RX_STATUS:
case TEGRA186_DSPK_RX_INT_STATUS:
case TEGRA186_DSPK_STATUS:
case TEGRA186_DSPK_INT_STATUS:
return true;
default:
return false;
}
}
static const struct regmap_config tegra186_dspk_regmap = {
.reg_bits = 32,
.reg_stride = 4,
.val_bits = 32,
.max_register = TEGRA186_DSPK_CODEC_CTRL,
.writeable_reg = tegra186_dspk_wr_reg,
.readable_reg = tegra186_dspk_rd_reg,
.volatile_reg = tegra186_dspk_volatile_reg,
.reg_defaults = tegra186_dspk_reg_defaults,
.num_reg_defaults = ARRAY_SIZE(tegra186_dspk_reg_defaults),
.reg_default_cb = regmap_default_zero_cb,
.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))
return dev_err_probe(dev, PTR_ERR(dspk->clk_dspk),
"can't retrieve DSPK clock\n");
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))
return dev_err_probe(dev, PTR_ERR(dspk->regmap),
"regmap init failed\n");
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)
return dev_err_probe(dev, err,
"can't register DSPK component\n");
pm_runtime_enable(dev);
return 0;
}
static void tegra186_dspk_platform_remove(struct platform_device *pdev)
{
pm_runtime_disable(&pdev->dev);
}
static const struct dev_pm_ops tegra186_dspk_pm_ops = {
RUNTIME_PM_OPS(tegra186_dspk_runtime_suspend,
tegra186_dspk_runtime_resume, NULL)
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 = pm_ptr(&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");
@@ -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
+459
View File
@@ -0,0 +1,459 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* tegra20_ac97.c - Tegra20 AC97 platform driver
*
* Copyright (c) 2012 Lucas Stach <dev@lynxeye.de>
*
* Partly based on code copyright/by:
*
* Copyright (c) 2011,2012 Toradex Inc.
*/
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/gpio/consumer.h>
#include <linux/io.h>
#include <linux/jiffies.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/reset.h>
#include <linux/slab.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/dmaengine_pcm.h>
#include "tegra20_ac97.h"
#define DRV_NAME "tegra20-ac97"
static struct tegra20_ac97 *workdata;
static void tegra20_ac97_codec_reset(struct snd_ac97 *ac97)
{
u32 readback;
unsigned long timeout;
/*
* The reset line is not driven by DAC pad group, have to toggle GPIO.
* The RESET line is active low but this is abstracted by the GPIO
* library.
*/
gpiod_set_value(workdata->reset_gpio, 1);
udelay(2);
gpiod_set_value(workdata->reset_gpio, 0);
udelay(2);
timeout = jiffies + msecs_to_jiffies(100);
do {
regmap_read(workdata->regmap, TEGRA20_AC97_STATUS1, &readback);
if (readback & TEGRA20_AC97_STATUS1_CODEC1_RDY)
break;
usleep_range(1000, 2000);
} while (!time_after(jiffies, timeout));
}
static void tegra20_ac97_codec_warm_reset(struct snd_ac97 *ac97)
{
u32 readback;
unsigned long timeout;
/*
* although sync line is driven by the DAC pad group warm reset using
* the controller cmd is not working, have to toggle sync line
* manually.
*/
gpiod_direction_output(workdata->sync_gpio, 1);
udelay(2);
gpiod_set_value(workdata->sync_gpio, 0);
udelay(2);
timeout = jiffies + msecs_to_jiffies(100);
do {
regmap_read(workdata->regmap, TEGRA20_AC97_STATUS1, &readback);
if (readback & TEGRA20_AC97_STATUS1_CODEC1_RDY)
break;
usleep_range(1000, 2000);
} while (!time_after(jiffies, timeout));
}
static unsigned short tegra20_ac97_codec_read(struct snd_ac97 *ac97_snd,
unsigned short reg)
{
u32 readback;
unsigned long timeout;
regmap_write(workdata->regmap, TEGRA20_AC97_CMD,
(((reg | 0x80) << TEGRA20_AC97_CMD_CMD_ADDR_SHIFT) &
TEGRA20_AC97_CMD_CMD_ADDR_MASK) |
TEGRA20_AC97_CMD_BUSY);
timeout = jiffies + msecs_to_jiffies(100);
do {
regmap_read(workdata->regmap, TEGRA20_AC97_STATUS1, &readback);
if (readback & TEGRA20_AC97_STATUS1_STA_VALID1)
break;
usleep_range(1000, 2000);
} while (!time_after(jiffies, timeout));
return ((readback & TEGRA20_AC97_STATUS1_STA_DATA1_MASK) >>
TEGRA20_AC97_STATUS1_STA_DATA1_SHIFT);
}
static void tegra20_ac97_codec_write(struct snd_ac97 *ac97_snd,
unsigned short reg, unsigned short val)
{
u32 readback;
unsigned long timeout;
regmap_write(workdata->regmap, TEGRA20_AC97_CMD,
((reg << TEGRA20_AC97_CMD_CMD_ADDR_SHIFT) &
TEGRA20_AC97_CMD_CMD_ADDR_MASK) |
((val << TEGRA20_AC97_CMD_CMD_DATA_SHIFT) &
TEGRA20_AC97_CMD_CMD_DATA_MASK) |
TEGRA20_AC97_CMD_BUSY);
timeout = jiffies + msecs_to_jiffies(100);
do {
regmap_read(workdata->regmap, TEGRA20_AC97_CMD, &readback);
if (!(readback & TEGRA20_AC97_CMD_BUSY))
break;
usleep_range(1000, 2000);
} while (!time_after(jiffies, timeout));
}
static struct snd_ac97_bus_ops tegra20_ac97_ops = {
.read = tegra20_ac97_codec_read,
.write = tegra20_ac97_codec_write,
.reset = tegra20_ac97_codec_reset,
.warm_reset = tegra20_ac97_codec_warm_reset,
};
static inline void tegra20_ac97_start_playback(struct tegra20_ac97 *ac97)
{
regmap_update_bits(ac97->regmap, TEGRA20_AC97_FIFO1_SCR,
TEGRA20_AC97_FIFO_SCR_PB_QRT_MT_EN,
TEGRA20_AC97_FIFO_SCR_PB_QRT_MT_EN);
regmap_update_bits(ac97->regmap, TEGRA20_AC97_CTRL,
TEGRA20_AC97_CTRL_PCM_DAC_EN |
TEGRA20_AC97_CTRL_STM_EN,
TEGRA20_AC97_CTRL_PCM_DAC_EN |
TEGRA20_AC97_CTRL_STM_EN);
}
static inline void tegra20_ac97_stop_playback(struct tegra20_ac97 *ac97)
{
regmap_update_bits(ac97->regmap, TEGRA20_AC97_FIFO1_SCR,
TEGRA20_AC97_FIFO_SCR_PB_QRT_MT_EN, 0);
regmap_update_bits(ac97->regmap, TEGRA20_AC97_CTRL,
TEGRA20_AC97_CTRL_PCM_DAC_EN, 0);
}
static inline void tegra20_ac97_start_capture(struct tegra20_ac97 *ac97)
{
regmap_update_bits(ac97->regmap, TEGRA20_AC97_FIFO1_SCR,
TEGRA20_AC97_FIFO_SCR_REC_FULL_EN,
TEGRA20_AC97_FIFO_SCR_REC_FULL_EN);
}
static inline void tegra20_ac97_stop_capture(struct tegra20_ac97 *ac97)
{
regmap_update_bits(ac97->regmap, TEGRA20_AC97_FIFO1_SCR,
TEGRA20_AC97_FIFO_SCR_REC_FULL_EN, 0);
}
static int tegra20_ac97_trigger(struct snd_pcm_substream *substream, int cmd,
struct snd_soc_dai *dai)
{
struct tegra20_ac97 *ac97 = snd_soc_dai_get_drvdata(dai);
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
case SNDRV_PCM_TRIGGER_RESUME:
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
tegra20_ac97_start_playback(ac97);
else
tegra20_ac97_start_capture(ac97);
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
case SNDRV_PCM_TRIGGER_SUSPEND:
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
tegra20_ac97_stop_playback(ac97);
else
tegra20_ac97_stop_capture(ac97);
break;
default:
return -EINVAL;
}
return 0;
}
static int tegra20_ac97_probe(struct snd_soc_dai *dai)
{
struct tegra20_ac97 *ac97 = snd_soc_dai_get_drvdata(dai);
snd_soc_dai_init_dma_data(dai, &ac97->playback_dma_data,
&ac97->capture_dma_data);
return 0;
}
static const struct snd_soc_dai_ops tegra20_ac97_dai_ops = {
.probe = tegra20_ac97_probe,
.trigger = tegra20_ac97_trigger,
};
static struct snd_soc_dai_driver tegra20_ac97_dai = {
.name = "tegra-ac97-pcm",
.playback = {
.stream_name = "PCM Playback",
.channels_min = 2,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_8000_48000,
.formats = SNDRV_PCM_FMTBIT_S16_LE,
},
.capture = {
.stream_name = "PCM Capture",
.channels_min = 2,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_8000_48000,
.formats = SNDRV_PCM_FMTBIT_S16_LE,
},
.ops = &tegra20_ac97_dai_ops,
};
static const struct snd_soc_component_driver tegra20_ac97_component = {
.name = DRV_NAME,
.legacy_dai_naming = 1,
};
static bool tegra20_ac97_wr_rd_reg(struct device *dev, unsigned int reg)
{
switch (reg) {
case TEGRA20_AC97_CTRL:
case TEGRA20_AC97_CMD:
case TEGRA20_AC97_STATUS1:
case TEGRA20_AC97_FIFO1_SCR:
case TEGRA20_AC97_FIFO_TX1:
case TEGRA20_AC97_FIFO_RX1:
return true;
default:
break;
}
return false;
}
static bool tegra20_ac97_volatile_reg(struct device *dev, unsigned int reg)
{
switch (reg) {
case TEGRA20_AC97_STATUS1:
case TEGRA20_AC97_FIFO1_SCR:
case TEGRA20_AC97_FIFO_TX1:
case TEGRA20_AC97_FIFO_RX1:
return true;
default:
break;
}
return false;
}
static bool tegra20_ac97_precious_reg(struct device *dev, unsigned int reg)
{
switch (reg) {
case TEGRA20_AC97_FIFO_TX1:
case TEGRA20_AC97_FIFO_RX1:
return true;
default:
break;
}
return false;
}
static const struct regmap_config tegra20_ac97_regmap_config = {
.reg_bits = 32,
.reg_stride = 4,
.val_bits = 32,
.max_register = TEGRA20_AC97_FIFO_RX1,
.writeable_reg = tegra20_ac97_wr_rd_reg,
.readable_reg = tegra20_ac97_wr_rd_reg,
.volatile_reg = tegra20_ac97_volatile_reg,
.precious_reg = tegra20_ac97_precious_reg,
.cache_type = REGCACHE_FLAT,
};
static int tegra20_ac97_platform_probe(struct platform_device *pdev)
{
struct tegra20_ac97 *ac97;
struct resource *mem;
void __iomem *regs;
int ret = 0;
ac97 = devm_kzalloc(&pdev->dev, sizeof(struct tegra20_ac97),
GFP_KERNEL);
if (!ac97) {
ret = -ENOMEM;
goto err;
}
dev_set_drvdata(&pdev->dev, ac97);
ac97->reset = devm_reset_control_get_exclusive(&pdev->dev, "ac97");
if (IS_ERR(ac97->reset)) {
dev_err(&pdev->dev, "Can't retrieve ac97 reset\n");
ret = PTR_ERR(ac97->reset);
goto err;
}
ac97->clk_ac97 = devm_clk_get(&pdev->dev, NULL);
if (IS_ERR(ac97->clk_ac97)) {
dev_err(&pdev->dev, "Can't retrieve ac97 clock\n");
ret = PTR_ERR(ac97->clk_ac97);
goto err;
}
regs = devm_platform_get_and_ioremap_resource(pdev, 0, &mem);
if (IS_ERR(regs)) {
ret = PTR_ERR(regs);
goto err_clk_put;
}
ac97->regmap = devm_regmap_init_mmio(&pdev->dev, regs,
&tegra20_ac97_regmap_config);
if (IS_ERR(ac97->regmap)) {
dev_err(&pdev->dev, "regmap init failed\n");
ret = PTR_ERR(ac97->regmap);
goto err_clk_put;
}
/* Obtain RESET de-asserted */
ac97->reset_gpio = devm_gpiod_get(&pdev->dev,
"nvidia,codec-reset",
GPIOD_OUT_LOW);
if (IS_ERR(ac97->reset_gpio)) {
ret = PTR_ERR(ac97->reset_gpio);
dev_err(&pdev->dev, "no RESET GPIO supplied: %d\n", ret);
goto err_clk_put;
}
gpiod_set_consumer_name(ac97->reset_gpio, "codec-reset");
ac97->sync_gpio = devm_gpiod_get(&pdev->dev,
"nvidia,codec-sync",
GPIOD_OUT_LOW);
if (IS_ERR(ac97->sync_gpio)) {
ret = PTR_ERR(ac97->sync_gpio);
dev_err(&pdev->dev, "no codec-sync GPIO supplied: %d\n", ret);
goto err_clk_put;
}
gpiod_set_consumer_name(ac97->sync_gpio, "codec-sync");
ac97->capture_dma_data.addr = mem->start + TEGRA20_AC97_FIFO_RX1;
ac97->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
ac97->capture_dma_data.maxburst = 4;
ac97->playback_dma_data.addr = mem->start + TEGRA20_AC97_FIFO_TX1;
ac97->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
ac97->playback_dma_data.maxburst = 4;
ret = reset_control_assert(ac97->reset);
if (ret) {
dev_err(&pdev->dev, "Failed to assert AC'97 reset: %d\n", ret);
goto err_clk_put;
}
ret = clk_prepare_enable(ac97->clk_ac97);
if (ret) {
dev_err(&pdev->dev, "clk_enable failed: %d\n", ret);
goto err_clk_put;
}
usleep_range(10, 100);
ret = reset_control_deassert(ac97->reset);
if (ret) {
dev_err(&pdev->dev, "Failed to deassert AC'97 reset: %d\n", ret);
goto err_clk_disable_unprepare;
}
ret = snd_soc_set_ac97_ops(&tegra20_ac97_ops);
if (ret) {
dev_err(&pdev->dev, "Failed to set AC'97 ops: %d\n", ret);
goto err_clk_disable_unprepare;
}
ret = snd_soc_register_component(&pdev->dev, &tegra20_ac97_component,
&tegra20_ac97_dai, 1);
if (ret) {
dev_err(&pdev->dev, "Could not register DAI: %d\n", ret);
ret = -ENOMEM;
goto err_clk_disable_unprepare;
}
ret = tegra_pcm_platform_register(&pdev->dev);
if (ret) {
dev_err(&pdev->dev, "Could not register PCM: %d\n", ret);
goto err_unregister_component;
}
/* XXX: crufty ASoC AC97 API - only one AC97 codec allowed */
workdata = ac97;
return 0;
err_unregister_component:
snd_soc_unregister_component(&pdev->dev);
err_clk_disable_unprepare:
clk_disable_unprepare(ac97->clk_ac97);
err_clk_put:
err:
snd_soc_set_ac97_ops(NULL);
return ret;
}
static void tegra20_ac97_platform_remove(struct platform_device *pdev)
{
struct tegra20_ac97 *ac97 = dev_get_drvdata(&pdev->dev);
tegra_pcm_platform_unregister(&pdev->dev);
snd_soc_unregister_component(&pdev->dev);
clk_disable_unprepare(ac97->clk_ac97);
snd_soc_set_ac97_ops(NULL);
}
static const struct of_device_id tegra20_ac97_of_match[] = {
{ .compatible = "nvidia,tegra20-ac97", },
{},
};
MODULE_DEVICE_TABLE(of, tegra20_ac97_of_match);
static struct platform_driver tegra20_ac97_driver = {
.driver = {
.name = DRV_NAME,
.of_match_table = tegra20_ac97_of_match,
},
.probe = tegra20_ac97_platform_probe,
.remove = tegra20_ac97_platform_remove,
};
module_platform_driver(tegra20_ac97_driver);
MODULE_AUTHOR("Lucas Stach");
MODULE_DESCRIPTION("Tegra20 AC97 ASoC driver");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:" DRV_NAME);
@@ -0,0 +1,86 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* tegra20_ac97.h - Definitions for the Tegra20 AC97 controller driver
*
* Copyright (c) 2012 Lucas Stach <dev@lynxeye.de>
*
* Partly based on code copyright/by:
*
* Copyright (c) 2011,2012 Toradex Inc.
*/
#ifndef __TEGRA20_AC97_H__
#define __TEGRA20_AC97_H__
#include "tegra_pcm.h"
#define TEGRA20_AC97_CTRL 0x00
#define TEGRA20_AC97_CMD 0x04
#define TEGRA20_AC97_STATUS1 0x08
/* ... */
#define TEGRA20_AC97_FIFO1_SCR 0x1c
/* ... */
#define TEGRA20_AC97_FIFO_TX1 0x40
#define TEGRA20_AC97_FIFO_RX1 0x80
/* TEGRA20_AC97_CTRL */
#define TEGRA20_AC97_CTRL_STM2_EN (1 << 16)
#define TEGRA20_AC97_CTRL_DOUBLE_SAMPLING_EN (1 << 11)
#define TEGRA20_AC97_CTRL_IO_CNTRL_EN (1 << 10)
#define TEGRA20_AC97_CTRL_HSET_DAC_EN (1 << 9)
#define TEGRA20_AC97_CTRL_LINE2_DAC_EN (1 << 8)
#define TEGRA20_AC97_CTRL_PCM_LFE_EN (1 << 7)
#define TEGRA20_AC97_CTRL_PCM_SUR_EN (1 << 6)
#define TEGRA20_AC97_CTRL_PCM_CEN_DAC_EN (1 << 5)
#define TEGRA20_AC97_CTRL_LINE1_DAC_EN (1 << 4)
#define TEGRA20_AC97_CTRL_PCM_DAC_EN (1 << 3)
#define TEGRA20_AC97_CTRL_COLD_RESET (1 << 2)
#define TEGRA20_AC97_CTRL_WARM_RESET (1 << 1)
#define TEGRA20_AC97_CTRL_STM_EN (1 << 0)
/* TEGRA20_AC97_CMD */
#define TEGRA20_AC97_CMD_CMD_ADDR_SHIFT 24
#define TEGRA20_AC97_CMD_CMD_ADDR_MASK (0xff << TEGRA20_AC97_CMD_CMD_ADDR_SHIFT)
#define TEGRA20_AC97_CMD_CMD_DATA_SHIFT 8
#define TEGRA20_AC97_CMD_CMD_DATA_MASK (0xffff << TEGRA20_AC97_CMD_CMD_DATA_SHIFT)
#define TEGRA20_AC97_CMD_CMD_ID_SHIFT 2
#define TEGRA20_AC97_CMD_CMD_ID_MASK (0x3 << TEGRA20_AC97_CMD_CMD_ID_SHIFT)
#define TEGRA20_AC97_CMD_BUSY (1 << 0)
/* TEGRA20_AC97_STATUS1 */
#define TEGRA20_AC97_STATUS1_STA_ADDR1_SHIFT 24
#define TEGRA20_AC97_STATUS1_STA_ADDR1_MASK (0xff << TEGRA20_AC97_STATUS1_STA_ADDR1_SHIFT)
#define TEGRA20_AC97_STATUS1_STA_DATA1_SHIFT 8
#define TEGRA20_AC97_STATUS1_STA_DATA1_MASK (0xffff << TEGRA20_AC97_STATUS1_STA_DATA1_SHIFT)
#define TEGRA20_AC97_STATUS1_STA_VALID1 (1 << 2)
#define TEGRA20_AC97_STATUS1_STANDBY1 (1 << 1)
#define TEGRA20_AC97_STATUS1_CODEC1_RDY (1 << 0)
/* TEGRA20_AC97_FIFO1_SCR */
#define TEGRA20_AC97_FIFO_SCR_REC_MT_CNT_SHIFT 27
#define TEGRA20_AC97_FIFO_SCR_REC_MT_CNT_MASK (0x1f << TEGRA20_AC97_FIFO_SCR_REC_MT_CNT_SHIFT)
#define TEGRA20_AC97_FIFO_SCR_PB_MT_CNT_SHIFT 22
#define TEGRA20_AC97_FIFO_SCR_PB_MT_CNT_MASK (0x1f << TEGRA20_AC97_FIFO_SCR_PB_MT_CNT_SHIFT)
#define TEGRA20_AC97_FIFO_SCR_REC_OVERRUN_INT_STA (1 << 19)
#define TEGRA20_AC97_FIFO_SCR_PB_UNDERRUN_INT_STA (1 << 18)
#define TEGRA20_AC97_FIFO_SCR_REC_FORCE_MT (1 << 17)
#define TEGRA20_AC97_FIFO_SCR_PB_FORCE_MT (1 << 16)
#define TEGRA20_AC97_FIFO_SCR_REC_FULL_EN (1 << 15)
#define TEGRA20_AC97_FIFO_SCR_REC_3QRT_FULL_EN (1 << 14)
#define TEGRA20_AC97_FIFO_SCR_REC_QRT_FULL_EN (1 << 13)
#define TEGRA20_AC97_FIFO_SCR_REC_EMPTY_EN (1 << 12)
#define TEGRA20_AC97_FIFO_SCR_PB_NOT_FULL_EN (1 << 11)
#define TEGRA20_AC97_FIFO_SCR_PB_QRT_MT_EN (1 << 10)
#define TEGRA20_AC97_FIFO_SCR_PB_3QRT_MT_EN (1 << 9)
#define TEGRA20_AC97_FIFO_SCR_PB_EMPTY_MT_EN (1 << 8)
struct tegra20_ac97 {
struct clk *clk_ac97;
struct snd_dmaengine_dai_dma_data capture_dma_data;
struct snd_dmaengine_dai_dma_data playback_dma_data;
struct reset_control *reset;
struct regmap *regmap;
struct gpio_desc *reset_gpio;
struct gpio_desc *sync_gpio;
};
#endif /* __TEGRA20_AC97_H__ */
+205
View File
@@ -0,0 +1,205 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* tegra20_das.c - Tegra20 DAS driver
*
* Author: Stephen Warren <swarren@nvidia.com>
* Copyright (C) 2010 - NVIDIA, Inc.
*/
#include <linux/device.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/slab.h>
#include <sound/soc.h>
#define DRV_NAME "tegra20-das"
/* Register TEGRA20_DAS_DAP_CTRL_SEL */
#define TEGRA20_DAS_DAP_CTRL_SEL 0x00
#define TEGRA20_DAS_DAP_CTRL_SEL_COUNT 5
#define TEGRA20_DAS_DAP_CTRL_SEL_STRIDE 4
#define TEGRA20_DAS_DAP_CTRL_SEL_DAP_MS_SEL_P 31
#define TEGRA20_DAS_DAP_CTRL_SEL_DAP_MS_SEL_S 1
#define TEGRA20_DAS_DAP_CTRL_SEL_DAP_SDATA1_TX_RX_P 30
#define TEGRA20_DAS_DAP_CTRL_SEL_DAP_SDATA1_TX_RX_S 1
#define TEGRA20_DAS_DAP_CTRL_SEL_DAP_SDATA2_TX_RX_P 29
#define TEGRA20_DAS_DAP_CTRL_SEL_DAP_SDATA2_TX_RX_S 1
#define TEGRA20_DAS_DAP_CTRL_SEL_DAP_CTRL_SEL_P 0
#define TEGRA20_DAS_DAP_CTRL_SEL_DAP_CTRL_SEL_S 5
/* Values for field TEGRA20_DAS_DAP_CTRL_SEL_DAP_CTRL_SEL */
#define TEGRA20_DAS_DAP_SEL_DAC1 0
#define TEGRA20_DAS_DAP_SEL_DAC2 1
#define TEGRA20_DAS_DAP_SEL_DAC3 2
#define TEGRA20_DAS_DAP_SEL_DAP1 16
#define TEGRA20_DAS_DAP_SEL_DAP2 17
#define TEGRA20_DAS_DAP_SEL_DAP3 18
#define TEGRA20_DAS_DAP_SEL_DAP4 19
#define TEGRA20_DAS_DAP_SEL_DAP5 20
/* Register TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL */
#define TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL 0x40
#define TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL_COUNT 3
#define TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL_STRIDE 4
#define TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA2_SEL_P 28
#define TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA2_SEL_S 4
#define TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA1_SEL_P 24
#define TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA1_SEL_S 4
#define TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_CLK_SEL_P 0
#define TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_CLK_SEL_S 4
/*
* Values for:
* TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA2_SEL
* TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA1_SEL
* TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_CLK_SEL
*/
#define TEGRA20_DAS_DAC_SEL_DAP1 0
#define TEGRA20_DAS_DAC_SEL_DAP2 1
#define TEGRA20_DAS_DAC_SEL_DAP3 2
#define TEGRA20_DAS_DAC_SEL_DAP4 3
#define TEGRA20_DAS_DAC_SEL_DAP5 4
/*
* Names/IDs of the DACs/DAPs.
*/
#define TEGRA20_DAS_DAP_ID_1 0
#define TEGRA20_DAS_DAP_ID_2 1
#define TEGRA20_DAS_DAP_ID_3 2
#define TEGRA20_DAS_DAP_ID_4 3
#define TEGRA20_DAS_DAP_ID_5 4
#define TEGRA20_DAS_DAC_ID_1 0
#define TEGRA20_DAS_DAC_ID_2 1
#define TEGRA20_DAS_DAC_ID_3 2
struct tegra20_das {
struct regmap *regmap;
};
/*
* Terminology:
* DAS: Digital audio switch (HW module controlled by this driver)
* DAP: Digital audio port (port/pins on Tegra device)
* DAC: Digital audio controller (e.g. I2S or AC97 controller elsewhere)
*
* The Tegra DAS is a mux/cross-bar which can connect each DAP to a specific
* DAC, or another DAP. When DAPs are connected, one must be the master and
* one the slave. Each DAC allows selection of a specific DAP for input, to
* cater for the case where N DAPs are connected to 1 DAC for broadcast
* output.
*
* This driver is dumb; no attempt is made to ensure that a valid routing
* configuration is programmed.
*/
static inline void tegra20_das_write(struct tegra20_das *das, u32 reg, u32 val)
{
regmap_write(das->regmap, reg, val);
}
static void tegra20_das_connect_dap_to_dac(struct tegra20_das *das, int dap, int dac)
{
u32 addr;
u32 reg;
addr = TEGRA20_DAS_DAP_CTRL_SEL +
(dap * TEGRA20_DAS_DAP_CTRL_SEL_STRIDE);
reg = dac << TEGRA20_DAS_DAP_CTRL_SEL_DAP_CTRL_SEL_P;
tegra20_das_write(das, addr, reg);
}
static void tegra20_das_connect_dac_to_dap(struct tegra20_das *das, int dac, int dap)
{
u32 addr;
u32 reg;
addr = TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL +
(dac * TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL_STRIDE);
reg = dap << TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_CLK_SEL_P |
dap << TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA1_SEL_P |
dap << TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA2_SEL_P;
tegra20_das_write(das, addr, reg);
}
#define LAST_REG(name) \
(TEGRA20_DAS_##name + \
(TEGRA20_DAS_##name##_STRIDE * (TEGRA20_DAS_##name##_COUNT - 1)))
static bool tegra20_das_wr_rd_reg(struct device *dev, unsigned int reg)
{
if (reg <= LAST_REG(DAP_CTRL_SEL))
return true;
if ((reg >= TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL) &&
(reg <= LAST_REG(DAC_INPUT_DATA_CLK_SEL)))
return true;
return false;
}
static const struct regmap_config tegra20_das_regmap_config = {
.reg_bits = 32,
.reg_stride = 4,
.val_bits = 32,
.max_register = LAST_REG(DAC_INPUT_DATA_CLK_SEL),
.writeable_reg = tegra20_das_wr_rd_reg,
.readable_reg = tegra20_das_wr_rd_reg,
.cache_type = REGCACHE_FLAT,
};
static int tegra20_das_probe(struct platform_device *pdev)
{
void __iomem *regs;
struct tegra20_das *das;
das = devm_kzalloc(&pdev->dev, sizeof(struct tegra20_das), GFP_KERNEL);
if (!das)
return -ENOMEM;
regs = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(regs))
return PTR_ERR(regs);
das->regmap = devm_regmap_init_mmio(&pdev->dev, regs,
&tegra20_das_regmap_config);
if (IS_ERR(das->regmap)) {
dev_err(&pdev->dev, "regmap init failed\n");
return PTR_ERR(das->regmap);
}
tegra20_das_connect_dap_to_dac(das, TEGRA20_DAS_DAP_ID_1,
TEGRA20_DAS_DAP_SEL_DAC1);
tegra20_das_connect_dac_to_dap(das, TEGRA20_DAS_DAC_ID_1,
TEGRA20_DAS_DAC_SEL_DAP1);
tegra20_das_connect_dap_to_dac(das, TEGRA20_DAS_DAP_ID_3,
TEGRA20_DAS_DAP_SEL_DAC3);
tegra20_das_connect_dac_to_dap(das, TEGRA20_DAS_DAC_ID_3,
TEGRA20_DAS_DAC_SEL_DAP3);
return 0;
}
static const struct of_device_id tegra20_das_of_match[] = {
{ .compatible = "nvidia,tegra20-das", },
{},
};
MODULE_DEVICE_TABLE(of, tegra20_das_of_match);
static struct platform_driver tegra20_das_driver = {
.probe = tegra20_das_probe,
.driver = {
.name = DRV_NAME,
.of_match_table = tegra20_das_of_match,
},
};
module_platform_driver(tegra20_das_driver);
MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>");
MODULE_DESCRIPTION("Tegra20 DAS driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:" DRV_NAME);
+510
View File
@@ -0,0 +1,510 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* tegra20_i2s.c - Tegra20 I2S driver
*
* Author: Stephen Warren <swarren@nvidia.com>
* Copyright (C) 2010,2012 - NVIDIA, Inc.
*
* Based on code copyright/by:
*
* Copyright (c) 2009-2010, NVIDIA Corporation.
* Scott Peterson <speterson@nvidia.com>
*
* Copyright (C) 2010 Google, Inc.
* Iliyan Malchev <malchev@google.com>
*/
#include <linux/clk.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include <linux/reset.h>
#include <linux/slab.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/dmaengine_pcm.h>
#include "tegra20_i2s.h"
#define DRV_NAME "tegra20-i2s"
static int tegra20_i2s_runtime_suspend(struct device *dev)
{
struct tegra20_i2s *i2s = dev_get_drvdata(dev);
regcache_cache_only(i2s->regmap, true);
clk_disable_unprepare(i2s->clk_i2s);
return 0;
}
static int tegra20_i2s_runtime_resume(struct device *dev)
{
struct tegra20_i2s *i2s = dev_get_drvdata(dev);
int ret;
ret = reset_control_assert(i2s->reset);
if (ret)
return ret;
ret = clk_prepare_enable(i2s->clk_i2s);
if (ret) {
dev_err(dev, "clk_enable failed: %d\n", ret);
return ret;
}
usleep_range(10, 100);
ret = reset_control_deassert(i2s->reset);
if (ret)
goto disable_clocks;
regcache_cache_only(i2s->regmap, false);
regcache_mark_dirty(i2s->regmap);
ret = regcache_sync(i2s->regmap);
if (ret)
goto disable_clocks;
return 0;
disable_clocks:
clk_disable_unprepare(i2s->clk_i2s);
return ret;
}
static int tegra20_i2s_set_fmt(struct snd_soc_dai *dai,
unsigned int fmt)
{
struct tegra20_i2s *i2s = snd_soc_dai_get_drvdata(dai);
unsigned int mask = 0, val = 0;
switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
case SND_SOC_DAIFMT_NB_NF:
break;
default:
return -EINVAL;
}
mask |= TEGRA20_I2S_CTRL_MASTER_ENABLE;
switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
case SND_SOC_DAIFMT_BP_FP:
val |= TEGRA20_I2S_CTRL_MASTER_ENABLE;
break;
case SND_SOC_DAIFMT_BC_FC:
break;
default:
return -EINVAL;
}
mask |= TEGRA20_I2S_CTRL_BIT_FORMAT_MASK |
TEGRA20_I2S_CTRL_LRCK_MASK;
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
case SND_SOC_DAIFMT_DSP_A:
val |= TEGRA20_I2S_CTRL_BIT_FORMAT_DSP;
val |= TEGRA20_I2S_CTRL_LRCK_L_LOW;
break;
case SND_SOC_DAIFMT_DSP_B:
val |= TEGRA20_I2S_CTRL_BIT_FORMAT_DSP;
val |= TEGRA20_I2S_CTRL_LRCK_R_LOW;
break;
case SND_SOC_DAIFMT_I2S:
val |= TEGRA20_I2S_CTRL_BIT_FORMAT_I2S;
val |= TEGRA20_I2S_CTRL_LRCK_L_LOW;
break;
case SND_SOC_DAIFMT_RIGHT_J:
val |= TEGRA20_I2S_CTRL_BIT_FORMAT_RJM;
val |= TEGRA20_I2S_CTRL_LRCK_L_LOW;
break;
case SND_SOC_DAIFMT_LEFT_J:
val |= TEGRA20_I2S_CTRL_BIT_FORMAT_LJM;
val |= TEGRA20_I2S_CTRL_LRCK_L_LOW;
break;
default:
return -EINVAL;
}
regmap_update_bits(i2s->regmap, TEGRA20_I2S_CTRL, mask, val);
return 0;
}
static int tegra20_i2s_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
struct device *dev = dai->dev;
struct tegra20_i2s *i2s = snd_soc_dai_get_drvdata(dai);
unsigned int mask, val;
int ret, sample_size, srate, i2sclock, bitcnt;
mask = TEGRA20_I2S_CTRL_BIT_SIZE_MASK;
switch (params_format(params)) {
case SNDRV_PCM_FORMAT_S16_LE:
val = TEGRA20_I2S_CTRL_BIT_SIZE_16;
sample_size = 16;
break;
case SNDRV_PCM_FORMAT_S24_LE:
val = TEGRA20_I2S_CTRL_BIT_SIZE_24;
sample_size = 24;
break;
case SNDRV_PCM_FORMAT_S32_LE:
val = TEGRA20_I2S_CTRL_BIT_SIZE_32;
sample_size = 32;
break;
default:
return -EINVAL;
}
mask |= TEGRA20_I2S_CTRL_FIFO_FORMAT_MASK;
val |= TEGRA20_I2S_CTRL_FIFO_FORMAT_PACKED;
regmap_update_bits(i2s->regmap, TEGRA20_I2S_CTRL, mask, val);
srate = params_rate(params);
/* Final "* 2" required by Tegra hardware */
i2sclock = srate * params_channels(params) * sample_size * 2;
ret = clk_set_rate(i2s->clk_i2s, i2sclock);
if (ret) {
dev_err(dev, "Can't set I2S clock rate: %d\n", ret);
return ret;
}
bitcnt = (i2sclock / (2 * srate)) - 1;
if (bitcnt < 0 || bitcnt > TEGRA20_I2S_TIMING_CHANNEL_BIT_COUNT_MASK_US)
return -EINVAL;
val = bitcnt << TEGRA20_I2S_TIMING_CHANNEL_BIT_COUNT_SHIFT;
if (i2sclock % (2 * srate))
val |= TEGRA20_I2S_TIMING_NON_SYM_ENABLE;
regmap_write(i2s->regmap, TEGRA20_I2S_TIMING, val);
regmap_write(i2s->regmap, TEGRA20_I2S_FIFO_SCR,
TEGRA20_I2S_FIFO_SCR_FIFO2_ATN_LVL_FOUR_SLOTS |
TEGRA20_I2S_FIFO_SCR_FIFO1_ATN_LVL_FOUR_SLOTS);
return 0;
}
static void tegra20_i2s_start_playback(struct tegra20_i2s *i2s)
{
regmap_update_bits(i2s->regmap, TEGRA20_I2S_CTRL,
TEGRA20_I2S_CTRL_FIFO1_ENABLE,
TEGRA20_I2S_CTRL_FIFO1_ENABLE);
}
static void tegra20_i2s_stop_playback(struct tegra20_i2s *i2s)
{
regmap_update_bits(i2s->regmap, TEGRA20_I2S_CTRL,
TEGRA20_I2S_CTRL_FIFO1_ENABLE, 0);
}
static void tegra20_i2s_start_capture(struct tegra20_i2s *i2s)
{
regmap_update_bits(i2s->regmap, TEGRA20_I2S_CTRL,
TEGRA20_I2S_CTRL_FIFO2_ENABLE,
TEGRA20_I2S_CTRL_FIFO2_ENABLE);
}
static void tegra20_i2s_stop_capture(struct tegra20_i2s *i2s)
{
regmap_update_bits(i2s->regmap, TEGRA20_I2S_CTRL,
TEGRA20_I2S_CTRL_FIFO2_ENABLE, 0);
}
static int tegra20_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
struct snd_soc_dai *dai)
{
struct tegra20_i2s *i2s = snd_soc_dai_get_drvdata(dai);
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
case SNDRV_PCM_TRIGGER_RESUME:
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
tegra20_i2s_start_playback(i2s);
else
tegra20_i2s_start_capture(i2s);
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
case SNDRV_PCM_TRIGGER_SUSPEND:
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
tegra20_i2s_stop_playback(i2s);
else
tegra20_i2s_stop_capture(i2s);
break;
default:
return -EINVAL;
}
return 0;
}
static int tegra20_i2s_probe(struct snd_soc_dai *dai)
{
struct tegra20_i2s *i2s = snd_soc_dai_get_drvdata(dai);
snd_soc_dai_init_dma_data(dai, &i2s->playback_dma_data,
&i2s->capture_dma_data);
return 0;
}
static const unsigned int tegra20_i2s_rates[] = {
8000, 11025, 16000, 22050, 32000, 44100, 48000, 64000, 88200, 96000
};
static int tegra20_i2s_filter_rates(struct snd_pcm_hw_params *params,
struct snd_pcm_hw_rule *rule)
{
struct snd_interval *r = hw_param_interval(params, rule->var);
struct snd_soc_dai *dai = rule->private;
struct tegra20_i2s *i2s = dev_get_drvdata(dai->dev);
struct clk *parent = clk_get_parent(i2s->clk_i2s);
unsigned long i, parent_rate, valid_rates = 0;
parent_rate = clk_get_rate(parent);
if (!parent_rate) {
dev_err(dai->dev, "Can't get parent clock rate\n");
return -EINVAL;
}
for (i = 0; i < ARRAY_SIZE(tegra20_i2s_rates); i++) {
if (parent_rate % (tegra20_i2s_rates[i] * 128) == 0)
valid_rates |= BIT(i);
}
/*
* At least one rate must be valid, otherwise the parent clock isn't
* audio PLL. Nothing should be filtered in this case.
*/
if (!valid_rates)
valid_rates = BIT(ARRAY_SIZE(tegra20_i2s_rates)) - 1;
return snd_interval_list(r, ARRAY_SIZE(tegra20_i2s_rates),
tegra20_i2s_rates, valid_rates);
}
static int tegra20_i2s_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
if (!device_property_read_bool(dai->dev, "nvidia,fixed-parent-rate"))
return 0;
return snd_pcm_hw_rule_add(substream->runtime, 0,
SNDRV_PCM_HW_PARAM_RATE,
tegra20_i2s_filter_rates, dai,
SNDRV_PCM_HW_PARAM_RATE, -1);
}
static const struct snd_soc_dai_ops tegra20_i2s_dai_ops = {
.probe = tegra20_i2s_probe,
.set_fmt = tegra20_i2s_set_fmt,
.hw_params = tegra20_i2s_hw_params,
.trigger = tegra20_i2s_trigger,
.startup = tegra20_i2s_startup,
};
static const struct snd_soc_dai_driver tegra20_i2s_dai_template = {
.playback = {
.stream_name = "Playback",
.channels_min = 2,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_8000_96000,
.formats = SNDRV_PCM_FMTBIT_S16_LE,
},
.capture = {
.stream_name = "Capture",
.channels_min = 2,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_8000_96000,
.formats = SNDRV_PCM_FMTBIT_S16_LE,
},
.ops = &tegra20_i2s_dai_ops,
.symmetric_rate = 1,
};
static const struct snd_soc_component_driver tegra20_i2s_component = {
.name = DRV_NAME,
.legacy_dai_naming = 1,
};
static bool tegra20_i2s_wr_rd_reg(struct device *dev, unsigned int reg)
{
switch (reg) {
case TEGRA20_I2S_CTRL:
case TEGRA20_I2S_STATUS:
case TEGRA20_I2S_TIMING:
case TEGRA20_I2S_FIFO_SCR:
case TEGRA20_I2S_PCM_CTRL:
case TEGRA20_I2S_NW_CTRL:
case TEGRA20_I2S_TDM_CTRL:
case TEGRA20_I2S_TDM_TX_RX_CTRL:
case TEGRA20_I2S_FIFO1:
case TEGRA20_I2S_FIFO2:
return true;
default:
return false;
}
}
static bool tegra20_i2s_volatile_reg(struct device *dev, unsigned int reg)
{
switch (reg) {
case TEGRA20_I2S_STATUS:
case TEGRA20_I2S_FIFO_SCR:
case TEGRA20_I2S_FIFO1:
case TEGRA20_I2S_FIFO2:
return true;
default:
return false;
}
}
static bool tegra20_i2s_precious_reg(struct device *dev, unsigned int reg)
{
switch (reg) {
case TEGRA20_I2S_FIFO1:
case TEGRA20_I2S_FIFO2:
return true;
default:
return false;
}
}
static const struct regmap_config tegra20_i2s_regmap_config = {
.reg_bits = 32,
.reg_stride = 4,
.val_bits = 32,
.max_register = TEGRA20_I2S_FIFO2,
.writeable_reg = tegra20_i2s_wr_rd_reg,
.readable_reg = tegra20_i2s_wr_rd_reg,
.volatile_reg = tegra20_i2s_volatile_reg,
.precious_reg = tegra20_i2s_precious_reg,
.cache_type = REGCACHE_FLAT,
};
static int tegra20_i2s_platform_probe(struct platform_device *pdev)
{
struct tegra20_i2s *i2s;
struct resource *mem;
void __iomem *regs;
int ret;
i2s = devm_kzalloc(&pdev->dev, sizeof(struct tegra20_i2s), GFP_KERNEL);
if (!i2s) {
ret = -ENOMEM;
goto err;
}
dev_set_drvdata(&pdev->dev, i2s);
i2s->dai = tegra20_i2s_dai_template;
i2s->dai.name = dev_name(&pdev->dev);
i2s->reset = devm_reset_control_get_exclusive(&pdev->dev, "i2s");
if (IS_ERR(i2s->reset)) {
dev_err(&pdev->dev, "Can't retrieve i2s reset\n");
return PTR_ERR(i2s->reset);
}
i2s->clk_i2s = devm_clk_get(&pdev->dev, NULL);
if (IS_ERR(i2s->clk_i2s)) {
dev_err(&pdev->dev, "Can't retrieve i2s clock\n");
ret = PTR_ERR(i2s->clk_i2s);
goto err;
}
regs = devm_platform_get_and_ioremap_resource(pdev, 0, &mem);
if (IS_ERR(regs)) {
ret = PTR_ERR(regs);
goto err;
}
i2s->regmap = devm_regmap_init_mmio(&pdev->dev, regs,
&tegra20_i2s_regmap_config);
if (IS_ERR(i2s->regmap)) {
dev_err(&pdev->dev, "regmap init failed\n");
ret = PTR_ERR(i2s->regmap);
goto err;
}
i2s->capture_dma_data.addr = mem->start + TEGRA20_I2S_FIFO2;
i2s->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
i2s->capture_dma_data.maxburst = 4;
i2s->playback_dma_data.addr = mem->start + TEGRA20_I2S_FIFO1;
i2s->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
i2s->playback_dma_data.maxburst = 4;
pm_runtime_enable(&pdev->dev);
ret = snd_soc_register_component(&pdev->dev, &tegra20_i2s_component,
&i2s->dai, 1);
if (ret) {
dev_err(&pdev->dev, "Could not register DAI: %d\n", ret);
ret = -ENOMEM;
goto err_pm_disable;
}
ret = tegra_pcm_platform_register(&pdev->dev);
if (ret) {
dev_err(&pdev->dev, "Could not register PCM: %d\n", ret);
goto err_unregister_component;
}
return 0;
err_unregister_component:
snd_soc_unregister_component(&pdev->dev);
err_pm_disable:
pm_runtime_disable(&pdev->dev);
err:
return ret;
}
static void tegra20_i2s_platform_remove(struct platform_device *pdev)
{
tegra_pcm_platform_unregister(&pdev->dev);
snd_soc_unregister_component(&pdev->dev);
pm_runtime_disable(&pdev->dev);
}
static const struct of_device_id tegra20_i2s_of_match[] = {
{ .compatible = "nvidia,tegra20-i2s", },
{},
};
MODULE_DEVICE_TABLE(of, tegra20_i2s_of_match);
static const struct dev_pm_ops tegra20_i2s_pm_ops = {
RUNTIME_PM_OPS(tegra20_i2s_runtime_suspend,
tegra20_i2s_runtime_resume, NULL)
SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume)
};
static struct platform_driver tegra20_i2s_driver = {
.driver = {
.name = DRV_NAME,
.of_match_table = tegra20_i2s_of_match,
.pm = pm_ptr(&tegra20_i2s_pm_ops),
},
.probe = tegra20_i2s_platform_probe,
.remove = tegra20_i2s_platform_remove,
};
module_platform_driver(tegra20_i2s_driver);
MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>");
MODULE_DESCRIPTION("Tegra20 I2S ASoC driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:" DRV_NAME);
+150
View File
@@ -0,0 +1,150 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* tegra20_i2s.h - Definitions for Tegra20 I2S driver
*
* Author: Stephen Warren <swarren@nvidia.com>
* Copyright (C) 2010,2012 - NVIDIA, Inc.
*
* Based on code copyright/by:
*
* Copyright (c) 2009-2010, NVIDIA Corporation.
* Scott Peterson <speterson@nvidia.com>
*
* Copyright (C) 2010 Google, Inc.
* Iliyan Malchev <malchev@google.com>
*/
#ifndef __TEGRA20_I2S_H__
#define __TEGRA20_I2S_H__
#include "tegra_pcm.h"
/* Register offsets from TEGRA20_I2S1_BASE and TEGRA20_I2S2_BASE */
#define TEGRA20_I2S_CTRL 0x00
#define TEGRA20_I2S_STATUS 0x04
#define TEGRA20_I2S_TIMING 0x08
#define TEGRA20_I2S_FIFO_SCR 0x0c
#define TEGRA20_I2S_PCM_CTRL 0x10
#define TEGRA20_I2S_NW_CTRL 0x14
#define TEGRA20_I2S_TDM_CTRL 0x20
#define TEGRA20_I2S_TDM_TX_RX_CTRL 0x24
#define TEGRA20_I2S_FIFO1 0x40
#define TEGRA20_I2S_FIFO2 0x80
/* Fields in TEGRA20_I2S_CTRL */
#define TEGRA20_I2S_CTRL_FIFO2_TX_ENABLE (1 << 30)
#define TEGRA20_I2S_CTRL_FIFO1_ENABLE (1 << 29)
#define TEGRA20_I2S_CTRL_FIFO2_ENABLE (1 << 28)
#define TEGRA20_I2S_CTRL_FIFO1_RX_ENABLE (1 << 27)
#define TEGRA20_I2S_CTRL_FIFO_LPBK_ENABLE (1 << 26)
#define TEGRA20_I2S_CTRL_MASTER_ENABLE (1 << 25)
#define TEGRA20_I2S_LRCK_LEFT_LOW 0
#define TEGRA20_I2S_LRCK_RIGHT_LOW 1
#define TEGRA20_I2S_CTRL_LRCK_SHIFT 24
#define TEGRA20_I2S_CTRL_LRCK_MASK (1 << TEGRA20_I2S_CTRL_LRCK_SHIFT)
#define TEGRA20_I2S_CTRL_LRCK_L_LOW (TEGRA20_I2S_LRCK_LEFT_LOW << TEGRA20_I2S_CTRL_LRCK_SHIFT)
#define TEGRA20_I2S_CTRL_LRCK_R_LOW (TEGRA20_I2S_LRCK_RIGHT_LOW << TEGRA20_I2S_CTRL_LRCK_SHIFT)
#define TEGRA20_I2S_BIT_FORMAT_I2S 0
#define TEGRA20_I2S_BIT_FORMAT_RJM 1
#define TEGRA20_I2S_BIT_FORMAT_LJM 2
#define TEGRA20_I2S_BIT_FORMAT_DSP 3
#define TEGRA20_I2S_CTRL_BIT_FORMAT_SHIFT 10
#define TEGRA20_I2S_CTRL_BIT_FORMAT_MASK (3 << TEGRA20_I2S_CTRL_BIT_FORMAT_SHIFT)
#define TEGRA20_I2S_CTRL_BIT_FORMAT_I2S (TEGRA20_I2S_BIT_FORMAT_I2S << TEGRA20_I2S_CTRL_BIT_FORMAT_SHIFT)
#define TEGRA20_I2S_CTRL_BIT_FORMAT_RJM (TEGRA20_I2S_BIT_FORMAT_RJM << TEGRA20_I2S_CTRL_BIT_FORMAT_SHIFT)
#define TEGRA20_I2S_CTRL_BIT_FORMAT_LJM (TEGRA20_I2S_BIT_FORMAT_LJM << TEGRA20_I2S_CTRL_BIT_FORMAT_SHIFT)
#define TEGRA20_I2S_CTRL_BIT_FORMAT_DSP (TEGRA20_I2S_BIT_FORMAT_DSP << TEGRA20_I2S_CTRL_BIT_FORMAT_SHIFT)
#define TEGRA20_I2S_BIT_SIZE_16 0
#define TEGRA20_I2S_BIT_SIZE_20 1
#define TEGRA20_I2S_BIT_SIZE_24 2
#define TEGRA20_I2S_BIT_SIZE_32 3
#define TEGRA20_I2S_CTRL_BIT_SIZE_SHIFT 8
#define TEGRA20_I2S_CTRL_BIT_SIZE_MASK (3 << TEGRA20_I2S_CTRL_BIT_SIZE_SHIFT)
#define TEGRA20_I2S_CTRL_BIT_SIZE_16 (TEGRA20_I2S_BIT_SIZE_16 << TEGRA20_I2S_CTRL_BIT_SIZE_SHIFT)
#define TEGRA20_I2S_CTRL_BIT_SIZE_20 (TEGRA20_I2S_BIT_SIZE_20 << TEGRA20_I2S_CTRL_BIT_SIZE_SHIFT)
#define TEGRA20_I2S_CTRL_BIT_SIZE_24 (TEGRA20_I2S_BIT_SIZE_24 << TEGRA20_I2S_CTRL_BIT_SIZE_SHIFT)
#define TEGRA20_I2S_CTRL_BIT_SIZE_32 (TEGRA20_I2S_BIT_SIZE_32 << TEGRA20_I2S_CTRL_BIT_SIZE_SHIFT)
#define TEGRA20_I2S_FIFO_16_LSB 0
#define TEGRA20_I2S_FIFO_20_LSB 1
#define TEGRA20_I2S_FIFO_24_LSB 2
#define TEGRA20_I2S_FIFO_32 3
#define TEGRA20_I2S_FIFO_PACKED 7
#define TEGRA20_I2S_CTRL_FIFO_FORMAT_SHIFT 4
#define TEGRA20_I2S_CTRL_FIFO_FORMAT_MASK (7 << TEGRA20_I2S_CTRL_FIFO_FORMAT_SHIFT)
#define TEGRA20_I2S_CTRL_FIFO_FORMAT_16_LSB (TEGRA20_I2S_FIFO_16_LSB << TEGRA20_I2S_CTRL_FIFO_FORMAT_SHIFT)
#define TEGRA20_I2S_CTRL_FIFO_FORMAT_20_LSB (TEGRA20_I2S_FIFO_20_LSB << TEGRA20_I2S_CTRL_FIFO_FORMAT_SHIFT)
#define TEGRA20_I2S_CTRL_FIFO_FORMAT_24_LSB (TEGRA20_I2S_FIFO_24_LSB << TEGRA20_I2S_CTRL_FIFO_FORMAT_SHIFT)
#define TEGRA20_I2S_CTRL_FIFO_FORMAT_32 (TEGRA20_I2S_FIFO_32 << TEGRA20_I2S_CTRL_FIFO_FORMAT_SHIFT)
#define TEGRA20_I2S_CTRL_FIFO_FORMAT_PACKED (TEGRA20_I2S_FIFO_PACKED << TEGRA20_I2S_CTRL_FIFO_FORMAT_SHIFT)
#define TEGRA20_I2S_CTRL_IE_FIFO1_ERR (1 << 3)
#define TEGRA20_I2S_CTRL_IE_FIFO2_ERR (1 << 2)
#define TEGRA20_I2S_CTRL_QE_FIFO1 (1 << 1)
#define TEGRA20_I2S_CTRL_QE_FIFO2 (1 << 0)
/* Fields in TEGRA20_I2S_STATUS */
#define TEGRA20_I2S_STATUS_FIFO1_RDY (1 << 31)
#define TEGRA20_I2S_STATUS_FIFO2_RDY (1 << 30)
#define TEGRA20_I2S_STATUS_FIFO1_BSY (1 << 29)
#define TEGRA20_I2S_STATUS_FIFO2_BSY (1 << 28)
#define TEGRA20_I2S_STATUS_FIFO1_ERR (1 << 3)
#define TEGRA20_I2S_STATUS_FIFO2_ERR (1 << 2)
#define TEGRA20_I2S_STATUS_QS_FIFO1 (1 << 1)
#define TEGRA20_I2S_STATUS_QS_FIFO2 (1 << 0)
/* Fields in TEGRA20_I2S_TIMING */
#define TEGRA20_I2S_TIMING_NON_SYM_ENABLE (1 << 12)
#define TEGRA20_I2S_TIMING_CHANNEL_BIT_COUNT_SHIFT 0
#define TEGRA20_I2S_TIMING_CHANNEL_BIT_COUNT_MASK_US 0x7ff
#define TEGRA20_I2S_TIMING_CHANNEL_BIT_COUNT_MASK (TEGRA20_I2S_TIMING_CHANNEL_BIT_COUNT_MASK_US << TEGRA20_I2S_TIMING_CHANNEL_BIT_COUNT_SHIFT)
/* Fields in TEGRA20_I2S_FIFO_SCR */
#define TEGRA20_I2S_FIFO_SCR_FIFO2_FULL_EMPTY_COUNT_SHIFT 24
#define TEGRA20_I2S_FIFO_SCR_FIFO1_FULL_EMPTY_COUNT_SHIFT 16
#define TEGRA20_I2S_FIFO_SCR_FIFO_FULL_EMPTY_COUNT_MASK 0x3f
#define TEGRA20_I2S_FIFO_SCR_FIFO2_CLR (1 << 12)
#define TEGRA20_I2S_FIFO_SCR_FIFO1_CLR (1 << 8)
#define TEGRA20_I2S_FIFO_ATN_LVL_ONE_SLOT 0
#define TEGRA20_I2S_FIFO_ATN_LVL_FOUR_SLOTS 1
#define TEGRA20_I2S_FIFO_ATN_LVL_EIGHT_SLOTS 2
#define TEGRA20_I2S_FIFO_ATN_LVL_TWELVE_SLOTS 3
#define TEGRA20_I2S_FIFO_SCR_FIFO2_ATN_LVL_SHIFT 4
#define TEGRA20_I2S_FIFO_SCR_FIFO2_ATN_LVL_MASK (3 << TEGRA20_I2S_FIFO_SCR_FIFO2_ATN_LVL_SHIFT)
#define TEGRA20_I2S_FIFO_SCR_FIFO2_ATN_LVL_ONE_SLOT (TEGRA20_I2S_FIFO_ATN_LVL_ONE_SLOT << TEGRA20_I2S_FIFO_SCR_FIFO2_ATN_LVL_SHIFT)
#define TEGRA20_I2S_FIFO_SCR_FIFO2_ATN_LVL_FOUR_SLOTS (TEGRA20_I2S_FIFO_ATN_LVL_FOUR_SLOTS << TEGRA20_I2S_FIFO_SCR_FIFO2_ATN_LVL_SHIFT)
#define TEGRA20_I2S_FIFO_SCR_FIFO2_ATN_LVL_EIGHT_SLOTS (TEGRA20_I2S_FIFO_ATN_LVL_EIGHT_SLOTS << TEGRA20_I2S_FIFO_SCR_FIFO2_ATN_LVL_SHIFT)
#define TEGRA20_I2S_FIFO_SCR_FIFO2_ATN_LVL_TWELVE_SLOTS (TEGRA20_I2S_FIFO_ATN_LVL_TWELVE_SLOTS << TEGRA20_I2S_FIFO_SCR_FIFO2_ATN_LVL_SHIFT)
#define TEGRA20_I2S_FIFO_SCR_FIFO1_ATN_LVL_SHIFT 0
#define TEGRA20_I2S_FIFO_SCR_FIFO1_ATN_LVL_MASK (3 << TEGRA20_I2S_FIFO_SCR_FIFO1_ATN_LVL_SHIFT)
#define TEGRA20_I2S_FIFO_SCR_FIFO1_ATN_LVL_ONE_SLOT (TEGRA20_I2S_FIFO_ATN_LVL_ONE_SLOT << TEGRA20_I2S_FIFO_SCR_FIFO1_ATN_LVL_SHIFT)
#define TEGRA20_I2S_FIFO_SCR_FIFO1_ATN_LVL_FOUR_SLOTS (TEGRA20_I2S_FIFO_ATN_LVL_FOUR_SLOTS << TEGRA20_I2S_FIFO_SCR_FIFO1_ATN_LVL_SHIFT)
#define TEGRA20_I2S_FIFO_SCR_FIFO1_ATN_LVL_EIGHT_SLOTS (TEGRA20_I2S_FIFO_ATN_LVL_EIGHT_SLOTS << TEGRA20_I2S_FIFO_SCR_FIFO1_ATN_LVL_SHIFT)
#define TEGRA20_I2S_FIFO_SCR_FIFO1_ATN_LVL_TWELVE_SLOTS (TEGRA20_I2S_FIFO_ATN_LVL_TWELVE_SLOTS << TEGRA20_I2S_FIFO_SCR_FIFO1_ATN_LVL_SHIFT)
struct tegra20_i2s {
struct snd_soc_dai_driver dai;
struct clk *clk_i2s;
struct snd_dmaengine_dai_dma_data capture_dma_data;
struct snd_dmaengine_dai_dma_data playback_dma_data;
struct regmap *regmap;
struct reset_control *reset;
};
#endif
+428
View File
@@ -0,0 +1,428 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* tegra20_spdif.c - Tegra20 SPDIF driver
*
* Author: Stephen Warren <swarren@nvidia.com>
* Copyright (C) 2011-2012 - NVIDIA, Inc.
*/
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include <linux/reset.h>
#include <linux/slab.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/dmaengine_pcm.h>
#include "tegra20_spdif.h"
static int tegra20_spdif_runtime_suspend(struct device *dev)
{
struct tegra20_spdif *spdif = dev_get_drvdata(dev);
regcache_cache_only(spdif->regmap, true);
clk_disable_unprepare(spdif->clk_spdif_out);
return 0;
}
static int tegra20_spdif_runtime_resume(struct device *dev)
{
struct tegra20_spdif *spdif = dev_get_drvdata(dev);
int ret;
ret = reset_control_assert(spdif->reset);
if (ret)
return ret;
ret = clk_prepare_enable(spdif->clk_spdif_out);
if (ret) {
dev_err(dev, "clk_enable failed: %d\n", ret);
return ret;
}
usleep_range(10, 100);
ret = reset_control_deassert(spdif->reset);
if (ret)
goto disable_clocks;
regcache_cache_only(spdif->regmap, false);
regcache_mark_dirty(spdif->regmap);
ret = regcache_sync(spdif->regmap);
if (ret)
goto disable_clocks;
return 0;
disable_clocks:
clk_disable_unprepare(spdif->clk_spdif_out);
return ret;
}
static int tegra20_spdif_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
struct tegra20_spdif *spdif = dev_get_drvdata(dai->dev);
unsigned int mask = 0, val = 0;
int ret, spdifclock;
long rate;
mask |= TEGRA20_SPDIF_CTRL_PACK |
TEGRA20_SPDIF_CTRL_BIT_MODE_MASK;
switch (params_format(params)) {
case SNDRV_PCM_FORMAT_S16_LE:
val |= TEGRA20_SPDIF_CTRL_PACK |
TEGRA20_SPDIF_CTRL_BIT_MODE_16BIT;
break;
default:
return -EINVAL;
}
regmap_update_bits(spdif->regmap, TEGRA20_SPDIF_CTRL, mask, val);
/*
* FIFO trigger level must be bigger than DMA burst or equal to it,
* otherwise data is discarded on overflow.
*/
regmap_update_bits(spdif->regmap, TEGRA20_SPDIF_DATA_FIFO_CSR,
TEGRA20_SPDIF_DATA_FIFO_CSR_TX_ATN_LVL_MASK,
TEGRA20_SPDIF_DATA_FIFO_CSR_TX_ATN_LVL_TU4_WORD_FULL);
switch (params_rate(params)) {
case 32000:
spdifclock = 4096000;
break;
case 44100:
spdifclock = 5644800;
break;
case 48000:
spdifclock = 6144000;
break;
case 88200:
spdifclock = 11289600;
break;
case 96000:
spdifclock = 12288000;
break;
case 176400:
spdifclock = 22579200;
break;
case 192000:
spdifclock = 24576000;
break;
default:
return -EINVAL;
}
ret = clk_set_rate(spdif->clk_spdif_out, spdifclock);
if (ret) {
dev_err(dai->dev, "Can't set SPDIF clock rate: %d\n", ret);
return ret;
}
rate = clk_get_rate(spdif->clk_spdif_out);
if (rate != spdifclock)
dev_warn_once(dai->dev,
"SPDIF clock rate %d doesn't match requested rate %lu\n",
spdifclock, rate);
return 0;
}
static void tegra20_spdif_start_playback(struct tegra20_spdif *spdif)
{
regmap_update_bits(spdif->regmap, TEGRA20_SPDIF_CTRL,
TEGRA20_SPDIF_CTRL_TX_EN,
TEGRA20_SPDIF_CTRL_TX_EN);
}
static void tegra20_spdif_stop_playback(struct tegra20_spdif *spdif)
{
regmap_update_bits(spdif->regmap, TEGRA20_SPDIF_CTRL,
TEGRA20_SPDIF_CTRL_TX_EN, 0);
}
static int tegra20_spdif_trigger(struct snd_pcm_substream *substream, int cmd,
struct snd_soc_dai *dai)
{
struct tegra20_spdif *spdif = dev_get_drvdata(dai->dev);
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
case SNDRV_PCM_TRIGGER_RESUME:
tegra20_spdif_start_playback(spdif);
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
case SNDRV_PCM_TRIGGER_SUSPEND:
tegra20_spdif_stop_playback(spdif);
break;
default:
return -EINVAL;
}
return 0;
}
static int tegra20_spdif_filter_rates(struct snd_pcm_hw_params *params,
struct snd_pcm_hw_rule *rule)
{
struct snd_interval *r = hw_param_interval(params, rule->var);
struct snd_soc_dai *dai = rule->private;
struct tegra20_spdif *spdif = dev_get_drvdata(dai->dev);
struct clk *parent = clk_get_parent(spdif->clk_spdif_out);
static const unsigned int rates[] = { 32000, 44100, 48000 };
unsigned long i, parent_rate, valid_rates = 0;
parent_rate = clk_get_rate(parent);
if (!parent_rate) {
dev_err(dai->dev, "Can't get parent clock rate\n");
return -EINVAL;
}
for (i = 0; i < ARRAY_SIZE(rates); i++) {
if (parent_rate % (rates[i] * 128) == 0)
valid_rates |= BIT(i);
}
/*
* At least one rate must be valid, otherwise the parent clock isn't
* audio PLL. Nothing should be filtered in this case.
*/
if (!valid_rates)
valid_rates = BIT(ARRAY_SIZE(rates)) - 1;
return snd_interval_list(r, ARRAY_SIZE(rates), rates, valid_rates);
}
static int tegra20_spdif_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
if (!device_property_read_bool(dai->dev, "nvidia,fixed-parent-rate"))
return 0;
/*
* SPDIF and I2S share audio PLL. HDMI takes audio packets from SPDIF
* and audio may not work on some TVs if clock rate isn't precise.
*
* PLL rate is controlled by I2S side. Filter out audio rates that
* don't match PLL rate at the start of stream to allow both SPDIF
* and I2S work simultaneously, assuming that PLL rate won't be
* changed later on.
*/
return snd_pcm_hw_rule_add(substream->runtime, 0,
SNDRV_PCM_HW_PARAM_RATE,
tegra20_spdif_filter_rates, dai,
SNDRV_PCM_HW_PARAM_RATE, -1);
}
static int tegra20_spdif_probe(struct snd_soc_dai *dai)
{
struct tegra20_spdif *spdif = dev_get_drvdata(dai->dev);
snd_soc_dai_init_dma_data(dai, &spdif->playback_dma_data, NULL);
return 0;
}
static const struct snd_soc_dai_ops tegra20_spdif_dai_ops = {
.probe = tegra20_spdif_probe,
.hw_params = tegra20_spdif_hw_params,
.trigger = tegra20_spdif_trigger,
.startup = tegra20_spdif_startup,
};
static struct snd_soc_dai_driver tegra20_spdif_dai = {
.name = "tegra20-spdif",
.playback = {
.stream_name = "Playback",
.channels_min = 2,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 |
SNDRV_PCM_RATE_48000,
.formats = SNDRV_PCM_FMTBIT_S16_LE,
},
.ops = &tegra20_spdif_dai_ops,
};
static const struct snd_soc_component_driver tegra20_spdif_component = {
.name = "tegra20-spdif",
.legacy_dai_naming = 1,
};
static bool tegra20_spdif_wr_rd_reg(struct device *dev, unsigned int reg)
{
switch (reg) {
case TEGRA20_SPDIF_CTRL:
case TEGRA20_SPDIF_STATUS:
case TEGRA20_SPDIF_STROBE_CTRL:
case TEGRA20_SPDIF_DATA_FIFO_CSR:
case TEGRA20_SPDIF_DATA_OUT:
case TEGRA20_SPDIF_DATA_IN:
case TEGRA20_SPDIF_CH_STA_RX_A:
case TEGRA20_SPDIF_CH_STA_RX_B:
case TEGRA20_SPDIF_CH_STA_RX_C:
case TEGRA20_SPDIF_CH_STA_RX_D:
case TEGRA20_SPDIF_CH_STA_RX_E:
case TEGRA20_SPDIF_CH_STA_RX_F:
case TEGRA20_SPDIF_CH_STA_TX_A:
case TEGRA20_SPDIF_CH_STA_TX_B:
case TEGRA20_SPDIF_CH_STA_TX_C:
case TEGRA20_SPDIF_CH_STA_TX_D:
case TEGRA20_SPDIF_CH_STA_TX_E:
case TEGRA20_SPDIF_CH_STA_TX_F:
case TEGRA20_SPDIF_USR_STA_RX_A:
case TEGRA20_SPDIF_USR_DAT_TX_A:
return true;
default:
return false;
}
}
static bool tegra20_spdif_volatile_reg(struct device *dev, unsigned int reg)
{
switch (reg) {
case TEGRA20_SPDIF_STATUS:
case TEGRA20_SPDIF_DATA_FIFO_CSR:
case TEGRA20_SPDIF_DATA_OUT:
case TEGRA20_SPDIF_DATA_IN:
case TEGRA20_SPDIF_CH_STA_RX_A:
case TEGRA20_SPDIF_CH_STA_RX_B:
case TEGRA20_SPDIF_CH_STA_RX_C:
case TEGRA20_SPDIF_CH_STA_RX_D:
case TEGRA20_SPDIF_CH_STA_RX_E:
case TEGRA20_SPDIF_CH_STA_RX_F:
case TEGRA20_SPDIF_USR_STA_RX_A:
case TEGRA20_SPDIF_USR_DAT_TX_A:
return true;
default:
return false;
}
}
static bool tegra20_spdif_precious_reg(struct device *dev, unsigned int reg)
{
switch (reg) {
case TEGRA20_SPDIF_DATA_OUT:
case TEGRA20_SPDIF_DATA_IN:
case TEGRA20_SPDIF_USR_STA_RX_A:
case TEGRA20_SPDIF_USR_DAT_TX_A:
return true;
default:
return false;
}
}
static const struct regmap_config tegra20_spdif_regmap_config = {
.reg_bits = 32,
.reg_stride = 4,
.val_bits = 32,
.max_register = TEGRA20_SPDIF_USR_DAT_TX_A,
.writeable_reg = tegra20_spdif_wr_rd_reg,
.readable_reg = tegra20_spdif_wr_rd_reg,
.volatile_reg = tegra20_spdif_volatile_reg,
.precious_reg = tegra20_spdif_precious_reg,
.cache_type = REGCACHE_FLAT,
};
static int tegra20_spdif_platform_probe(struct platform_device *pdev)
{
struct tegra20_spdif *spdif;
struct resource *mem;
void __iomem *regs;
int ret;
spdif = devm_kzalloc(&pdev->dev, sizeof(struct tegra20_spdif),
GFP_KERNEL);
if (!spdif)
return -ENOMEM;
dev_set_drvdata(&pdev->dev, spdif);
spdif->reset = devm_reset_control_get_exclusive(&pdev->dev, NULL);
if (IS_ERR(spdif->reset)) {
dev_err(&pdev->dev, "Can't retrieve spdif reset\n");
return PTR_ERR(spdif->reset);
}
spdif->clk_spdif_out = devm_clk_get(&pdev->dev, "out");
if (IS_ERR(spdif->clk_spdif_out)) {
dev_err(&pdev->dev, "Could not retrieve spdif clock\n");
return PTR_ERR(spdif->clk_spdif_out);
}
regs = devm_platform_get_and_ioremap_resource(pdev, 0, &mem);
if (IS_ERR(regs))
return PTR_ERR(regs);
spdif->regmap = devm_regmap_init_mmio(&pdev->dev, regs,
&tegra20_spdif_regmap_config);
if (IS_ERR(spdif->regmap)) {
dev_err(&pdev->dev, "regmap init failed\n");
return PTR_ERR(spdif->regmap);
}
spdif->playback_dma_data.addr = mem->start + TEGRA20_SPDIF_DATA_OUT;
spdif->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
spdif->playback_dma_data.maxburst = 4;
ret = devm_pm_runtime_enable(&pdev->dev);
if (ret)
return ret;
ret = devm_snd_soc_register_component(&pdev->dev,
&tegra20_spdif_component,
&tegra20_spdif_dai, 1);
if (ret) {
dev_err(&pdev->dev, "Could not register DAI: %d\n", ret);
return ret;
}
ret = devm_tegra_pcm_platform_register(&pdev->dev);
if (ret) {
dev_err(&pdev->dev, "Could not register PCM: %d\n", ret);
return ret;
}
return 0;
}
static const struct dev_pm_ops tegra20_spdif_pm_ops = {
RUNTIME_PM_OPS(tegra20_spdif_runtime_suspend,
tegra20_spdif_runtime_resume, NULL)
SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume)
};
static const struct of_device_id tegra20_spdif_of_match[] = {
{ .compatible = "nvidia,tegra20-spdif", },
{},
};
MODULE_DEVICE_TABLE(of, tegra20_spdif_of_match);
static struct platform_driver tegra20_spdif_driver = {
.driver = {
.name = "tegra20-spdif",
.pm = pm_ptr(&tegra20_spdif_pm_ops),
.of_match_table = tegra20_spdif_of_match,
},
.probe = tegra20_spdif_platform_probe,
};
module_platform_driver(tegra20_spdif_driver);
MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>");
MODULE_DESCRIPTION("Tegra20 SPDIF ASoC driver");
MODULE_LICENSE("GPL");
+457
View File
@@ -0,0 +1,457 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* tegra20_spdif.h - Definitions for Tegra20 SPDIF driver
*
* Author: Stephen Warren <swarren@nvidia.com>
* Copyright (C) 2011 - NVIDIA, Inc.
*
* Based on code copyright/by:
* Copyright (c) 2008-2009, NVIDIA Corporation
*/
#ifndef __TEGRA20_SPDIF_H__
#define __TEGRA20_SPDIF_H__
#include "tegra_pcm.h"
/* Offsets from TEGRA20_SPDIF_BASE */
#define TEGRA20_SPDIF_CTRL 0x0
#define TEGRA20_SPDIF_STATUS 0x4
#define TEGRA20_SPDIF_STROBE_CTRL 0x8
#define TEGRA20_SPDIF_DATA_FIFO_CSR 0x0C
#define TEGRA20_SPDIF_DATA_OUT 0x40
#define TEGRA20_SPDIF_DATA_IN 0x80
#define TEGRA20_SPDIF_CH_STA_RX_A 0x100
#define TEGRA20_SPDIF_CH_STA_RX_B 0x104
#define TEGRA20_SPDIF_CH_STA_RX_C 0x108
#define TEGRA20_SPDIF_CH_STA_RX_D 0x10C
#define TEGRA20_SPDIF_CH_STA_RX_E 0x110
#define TEGRA20_SPDIF_CH_STA_RX_F 0x114
#define TEGRA20_SPDIF_CH_STA_TX_A 0x140
#define TEGRA20_SPDIF_CH_STA_TX_B 0x144
#define TEGRA20_SPDIF_CH_STA_TX_C 0x148
#define TEGRA20_SPDIF_CH_STA_TX_D 0x14C
#define TEGRA20_SPDIF_CH_STA_TX_E 0x150
#define TEGRA20_SPDIF_CH_STA_TX_F 0x154
#define TEGRA20_SPDIF_USR_STA_RX_A 0x180
#define TEGRA20_SPDIF_USR_DAT_TX_A 0x1C0
/* Fields in TEGRA20_SPDIF_CTRL */
/* Start capturing from 0=right, 1=left channel */
#define TEGRA20_SPDIF_CTRL_CAP_LC (1 << 30)
/* SPDIF receiver(RX) enable */
#define TEGRA20_SPDIF_CTRL_RX_EN (1 << 29)
/* SPDIF Transmitter(TX) enable */
#define TEGRA20_SPDIF_CTRL_TX_EN (1 << 28)
/* Transmit Channel status */
#define TEGRA20_SPDIF_CTRL_TC_EN (1 << 27)
/* Transmit user Data */
#define TEGRA20_SPDIF_CTRL_TU_EN (1 << 26)
/* Interrupt on transmit error */
#define TEGRA20_SPDIF_CTRL_IE_TXE (1 << 25)
/* Interrupt on receive error */
#define TEGRA20_SPDIF_CTRL_IE_RXE (1 << 24)
/* Interrupt on invalid preamble */
#define TEGRA20_SPDIF_CTRL_IE_P (1 << 23)
/* Interrupt on "B" preamble */
#define TEGRA20_SPDIF_CTRL_IE_B (1 << 22)
/* Interrupt when block of channel status received */
#define TEGRA20_SPDIF_CTRL_IE_C (1 << 21)
/* Interrupt when a valid information unit (IU) is received */
#define TEGRA20_SPDIF_CTRL_IE_U (1 << 20)
/* Interrupt when RX user FIFO attention level is reached */
#define TEGRA20_SPDIF_CTRL_QE_RU (1 << 19)
/* Interrupt when TX user FIFO attention level is reached */
#define TEGRA20_SPDIF_CTRL_QE_TU (1 << 18)
/* Interrupt when RX data FIFO attention level is reached */
#define TEGRA20_SPDIF_CTRL_QE_RX (1 << 17)
/* Interrupt when TX data FIFO attention level is reached */
#define TEGRA20_SPDIF_CTRL_QE_TX (1 << 16)
/* Loopback test mode enable */
#define TEGRA20_SPDIF_CTRL_LBK_EN (1 << 15)
/*
* Pack data mode:
* 0 = Single data (16 bit needs to be padded to match the
* interface data bit size).
* 1 = Packeted left/right channel data into a single word.
*/
#define TEGRA20_SPDIF_CTRL_PACK (1 << 14)
/*
* 00 = 16bit data
* 01 = 20bit data
* 10 = 24bit data
* 11 = raw data
*/
#define TEGRA20_SPDIF_BIT_MODE_16BIT 0
#define TEGRA20_SPDIF_BIT_MODE_20BIT 1
#define TEGRA20_SPDIF_BIT_MODE_24BIT 2
#define TEGRA20_SPDIF_BIT_MODE_RAW 3
#define TEGRA20_SPDIF_CTRL_BIT_MODE_SHIFT 12
#define TEGRA20_SPDIF_CTRL_BIT_MODE_MASK (3 << TEGRA20_SPDIF_CTRL_BIT_MODE_SHIFT)
#define TEGRA20_SPDIF_CTRL_BIT_MODE_16BIT (TEGRA20_SPDIF_BIT_MODE_16BIT << TEGRA20_SPDIF_CTRL_BIT_MODE_SHIFT)
#define TEGRA20_SPDIF_CTRL_BIT_MODE_20BIT (TEGRA20_SPDIF_BIT_MODE_20BIT << TEGRA20_SPDIF_CTRL_BIT_MODE_SHIFT)
#define TEGRA20_SPDIF_CTRL_BIT_MODE_24BIT (TEGRA20_SPDIF_BIT_MODE_24BIT << TEGRA20_SPDIF_CTRL_BIT_MODE_SHIFT)
#define TEGRA20_SPDIF_CTRL_BIT_MODE_RAW (TEGRA20_SPDIF_BIT_MODE_RAW << TEGRA20_SPDIF_CTRL_BIT_MODE_SHIFT)
/* Fields in TEGRA20_SPDIF_STATUS */
/*
* Note: IS_P, IS_B, IS_C, and IS_U are sticky bits. Software must
* write a 1 to the corresponding bit location to clear the status.
*/
/*
* Receiver(RX) shifter is busy receiving data.
* This bit is asserted when the receiver first locked onto the
* preamble of the data stream after RX_EN is asserted. This bit is
* deasserted when either,
* (a) the end of a frame is reached after RX_EN is deeasserted, or
* (b) the SPDIF data stream becomes inactive.
*/
#define TEGRA20_SPDIF_STATUS_RX_BSY (1 << 29)
/*
* Transmitter(TX) shifter is busy transmitting data.
* This bit is asserted when TX_EN is asserted.
* This bit is deasserted when the end of a frame is reached after
* TX_EN is deasserted.
*/
#define TEGRA20_SPDIF_STATUS_TX_BSY (1 << 28)
/*
* TX is busy shifting out channel status.
* This bit is asserted when both TX_EN and TC_EN are asserted and
* data from CH_STA_TX_A register is loaded into the internal shifter.
* This bit is deasserted when either,
* (a) the end of a frame is reached after TX_EN is deasserted, or
* (b) CH_STA_TX_F register is loaded into the internal shifter.
*/
#define TEGRA20_SPDIF_STATUS_TC_BSY (1 << 27)
/*
* TX User data FIFO busy.
* This bit is asserted when TX_EN and TXU_EN are asserted and
* there's data in the TX user FIFO. This bit is deassert when either,
* (a) the end of a frame is reached after TX_EN is deasserted, or
* (b) there's no data left in the TX user FIFO.
*/
#define TEGRA20_SPDIF_STATUS_TU_BSY (1 << 26)
/* TX FIFO Underrun error status */
#define TEGRA20_SPDIF_STATUS_TX_ERR (1 << 25)
/* RX FIFO Overrun error status */
#define TEGRA20_SPDIF_STATUS_RX_ERR (1 << 24)
/* Preamble status: 0=Preamble OK, 1=bad/missing preamble */
#define TEGRA20_SPDIF_STATUS_IS_P (1 << 23)
/* B-preamble detection status: 0=not detected, 1=B-preamble detected */
#define TEGRA20_SPDIF_STATUS_IS_B (1 << 22)
/*
* RX channel block data receive status:
* 0=entire block not received yet.
* 1=received entire block of channel status,
*/
#define TEGRA20_SPDIF_STATUS_IS_C (1 << 21)
/* RX User Data Valid flag: 1=valid IU detected, 0 = no IU detected. */
#define TEGRA20_SPDIF_STATUS_IS_U (1 << 20)
/*
* RX User FIFO Status:
* 1=attention level reached, 0=attention level not reached.
*/
#define TEGRA20_SPDIF_STATUS_QS_RU (1 << 19)
/*
* TX User FIFO Status:
* 1=attention level reached, 0=attention level not reached.
*/
#define TEGRA20_SPDIF_STATUS_QS_TU (1 << 18)
/*
* RX Data FIFO Status:
* 1=attention level reached, 0=attention level not reached.
*/
#define TEGRA20_SPDIF_STATUS_QS_RX (1 << 17)
/*
* TX Data FIFO Status:
* 1=attention level reached, 0=attention level not reached.
*/
#define TEGRA20_SPDIF_STATUS_QS_TX (1 << 16)
/* Fields in TEGRA20_SPDIF_STROBE_CTRL */
/*
* Indicates the approximate number of detected SPDIFIN clocks within a
* bi-phase period.
*/
#define TEGRA20_SPDIF_STROBE_CTRL_PERIOD_SHIFT 16
#define TEGRA20_SPDIF_STROBE_CTRL_PERIOD_MASK (0xff << TEGRA20_SPDIF_STROBE_CTRL_PERIOD_SHIFT)
/* Data strobe mode: 0=Auto-locked 1=Manual locked */
#define TEGRA20_SPDIF_STROBE_CTRL_STROBE (1 << 15)
/*
* Manual data strobe time within the bi-phase clock period (in terms of
* the number of over-sampling clocks).
*/
#define TEGRA20_SPDIF_STROBE_CTRL_DATA_STROBES_SHIFT 8
#define TEGRA20_SPDIF_STROBE_CTRL_DATA_STROBES_MASK (0x1f << TEGRA20_SPDIF_STROBE_CTRL_DATA_STROBES_SHIFT)
/*
* Manual SPDIFIN bi-phase clock period (in terms of the number of
* over-sampling clocks).
*/
#define TEGRA20_SPDIF_STROBE_CTRL_CLOCK_PERIOD_SHIFT 0
#define TEGRA20_SPDIF_STROBE_CTRL_CLOCK_PERIOD_MASK (0x3f << TEGRA20_SPDIF_STROBE_CTRL_CLOCK_PERIOD_SHIFT)
/* Fields in SPDIF_DATA_FIFO_CSR */
/* Clear Receiver User FIFO (RX USR.FIFO) */
#define TEGRA20_SPDIF_DATA_FIFO_CSR_RU_CLR (1 << 31)
#define TEGRA20_SPDIF_FIFO_ATN_LVL_U_ONE_SLOT 0
#define TEGRA20_SPDIF_FIFO_ATN_LVL_U_TWO_SLOTS 1
#define TEGRA20_SPDIF_FIFO_ATN_LVL_U_THREE_SLOTS 2
#define TEGRA20_SPDIF_FIFO_ATN_LVL_U_FOUR_SLOTS 3
/* RU FIFO attention level */
#define TEGRA20_SPDIF_DATA_FIFO_CSR_RU_ATN_LVL_SHIFT 29
#define TEGRA20_SPDIF_DATA_FIFO_CSR_RU_ATN_LVL_MASK \
(0x3 << TEGRA20_SPDIF_DATA_FIFO_CSR_RU_ATN_LVL_SHIFT)
#define TEGRA20_SPDIF_DATA_FIFO_CSR_RU_ATN_LVL_RU1_WORD_FULL \
(TEGRA20_SPDIF_FIFO_ATN_LVL_U_ONE_SLOT << TEGRA20_SPDIF_DATA_FIFO_CSR_RU_ATN_LVL_SHIFT)
#define TEGRA20_SPDIF_DATA_FIFO_CSR_RU_ATN_LVL_RU2_WORD_FULL \
(TEGRA20_SPDIF_FIFO_ATN_LVL_U_TWO_SLOTS << TEGRA20_SPDIF_DATA_FIFO_CSR_RU_ATN_LVL_SHIFT)
#define TEGRA20_SPDIF_DATA_FIFO_CSR_RU_ATN_LVL_RU3_WORD_FULL \
(TEGRA20_SPDIF_FIFO_ATN_LVL_U_THREE_SLOTS << TEGRA20_SPDIF_DATA_FIFO_CSR_RU_ATN_LVL_SHIFT)
#define TEGRA20_SPDIF_DATA_FIFO_CSR_RU_ATN_LVL_RU4_WORD_FULL \
(TEGRA20_SPDIF_FIFO_ATN_LVL_U_FOUR_SLOTS << TEGRA20_SPDIF_DATA_FIFO_CSR_RU_ATN_LVL_SHIFT)
/* Number of RX USR.FIFO levels with valid data. */
#define TEGRA20_SPDIF_DATA_FIFO_CSR_RU_FULL_COUNT_SHIFT 24
#define TEGRA20_SPDIF_DATA_FIFO_CSR_RU_FULL_COUNT_MASK (0x1f << TEGRA20_SPDIF_DATA_FIFO_CSR_RU_FULL_COUNT_SHIFT)
/* Clear Transmitter User FIFO (TX USR.FIFO) */
#define TEGRA20_SPDIF_DATA_FIFO_CSR_TU_CLR (1 << 23)
/* TU FIFO attention level */
#define TEGRA20_SPDIF_DATA_FIFO_CSR_TU_ATN_LVL_SHIFT 21
#define TEGRA20_SPDIF_DATA_FIFO_CSR_TU_ATN_LVL_MASK \
(0x3 << TEGRA20_SPDIF_DATA_FIFO_CSR_TU_ATN_LVL_SHIFT)
#define TEGRA20_SPDIF_DATA_FIFO_CSR_TU_ATN_LVL_TU1_WORD_FULL \
(TEGRA20_SPDIF_FIFO_ATN_LVL_U_ONE_SLOT << TEGRA20_SPDIF_DATA_FIFO_CSR_TU_ATN_LVL_SHIFT)
#define TEGRA20_SPDIF_DATA_FIFO_CSR_TU_ATN_LVL_TU2_WORD_FULL \
(TEGRA20_SPDIF_FIFO_ATN_LVL_U_TWO_SLOTS << TEGRA20_SPDIF_DATA_FIFO_CSR_TU_ATN_LVL_SHIFT)
#define TEGRA20_SPDIF_DATA_FIFO_CSR_TU_ATN_LVL_TU3_WORD_FULL \
(TEGRA20_SPDIF_FIFO_ATN_LVL_U_THREE_SLOTS << TEGRA20_SPDIF_DATA_FIFO_CSR_TU_ATN_LVL_SHIFT)
#define TEGRA20_SPDIF_DATA_FIFO_CSR_TU_ATN_LVL_TU4_WORD_FULL \
(TEGRA20_SPDIF_FIFO_ATN_LVL_U_FOUR_SLOTS << TEGRA20_SPDIF_DATA_FIFO_CSR_TU_ATN_LVL_SHIFT)
/* Number of TX USR.FIFO levels that could be filled. */
#define TEGRA20_SPDIF_DATA_FIFO_CSR_TU_EMPTY_COUNT_SHIFT 16
#define TEGRA20_SPDIF_DATA_FIFO_CSR_TU_EMPTY_COUNT_MASK (0x1f << SPDIF_DATA_FIFO_CSR_TU_EMPTY_COUNT_SHIFT)
/* Clear Receiver Data FIFO (RX DATA.FIFO) */
#define TEGRA20_SPDIF_DATA_FIFO_CSR_RX_CLR (1 << 15)
#define TEGRA20_SPDIF_FIFO_ATN_LVL_D_ONE_SLOT 0
#define TEGRA20_SPDIF_FIFO_ATN_LVL_D_FOUR_SLOTS 1
#define TEGRA20_SPDIF_FIFO_ATN_LVL_D_EIGHT_SLOTS 2
#define TEGRA20_SPDIF_FIFO_ATN_LVL_D_TWELVE_SLOTS 3
/* RU FIFO attention level */
#define TEGRA20_SPDIF_DATA_FIFO_CSR_RX_ATN_LVL_SHIFT 13
#define TEGRA20_SPDIF_DATA_FIFO_CSR_RX_ATN_LVL_MASK \
(0x3 << TEGRA20_SPDIF_DATA_FIFO_CSR_RX_ATN_LVL_SHIFT)
#define TEGRA20_SPDIF_DATA_FIFO_CSR_RX_ATN_LVL_RU1_WORD_FULL \
(TEGRA20_SPDIF_FIFO_ATN_LVL_D_ONE_SLOT << TEGRA20_SPDIF_DATA_FIFO_CSR_RX_ATN_LVL_SHIFT)
#define TEGRA20_SPDIF_DATA_FIFO_CSR_RX_ATN_LVL_RU4_WORD_FULL \
(TEGRA20_SPDIF_FIFO_ATN_LVL_D_FOUR_SLOTS << TEGRA20_SPDIF_DATA_FIFO_CSR_RX_ATN_LVL_SHIFT)
#define TEGRA20_SPDIF_DATA_FIFO_CSR_RX_ATN_LVL_RU8_WORD_FULL \
(TEGRA20_SPDIF_FIFO_ATN_LVL_D_EIGHT_SLOTS << TEGRA20_SPDIF_DATA_FIFO_CSR_RX_ATN_LVL_SHIFT)
#define TEGRA20_SPDIF_DATA_FIFO_CSR_RX_ATN_LVL_RU12_WORD_FULL \
(TEGRA20_SPDIF_FIFO_ATN_LVL_D_TWELVE_SLOTS << TEGRA20_SPDIF_DATA_FIFO_CSR_RX_ATN_LVL_SHIFT)
/* Number of RX DATA.FIFO levels with valid data. */
#define TEGRA20_SPDIF_DATA_FIFO_CSR_RX_FULL_COUNT_SHIFT 8
#define TEGRA20_SPDIF_DATA_FIFO_CSR_RX_FULL_COUNT_MASK (0x1f << TEGRA20_SPDIF_DATA_FIFO_CSR_RX_FULL_COUNT_SHIFT)
/* Clear Transmitter Data FIFO (TX DATA.FIFO) */
#define TEGRA20_SPDIF_DATA_FIFO_CSR_TX_CLR (1 << 7)
/* TU FIFO attention level */
#define TEGRA20_SPDIF_DATA_FIFO_CSR_TX_ATN_LVL_SHIFT 5
#define TEGRA20_SPDIF_DATA_FIFO_CSR_TX_ATN_LVL_MASK \
(0x3 << TEGRA20_SPDIF_DATA_FIFO_CSR_TX_ATN_LVL_SHIFT)
#define TEGRA20_SPDIF_DATA_FIFO_CSR_TX_ATN_LVL_TU1_WORD_FULL \
(TEGRA20_SPDIF_FIFO_ATN_LVL_D_ONE_SLOT << TEGRA20_SPDIF_DATA_FIFO_CSR_TX_ATN_LVL_SHIFT)
#define TEGRA20_SPDIF_DATA_FIFO_CSR_TX_ATN_LVL_TU4_WORD_FULL \
(TEGRA20_SPDIF_FIFO_ATN_LVL_D_FOUR_SLOTS << TEGRA20_SPDIF_DATA_FIFO_CSR_TX_ATN_LVL_SHIFT)
#define TEGRA20_SPDIF_DATA_FIFO_CSR_TX_ATN_LVL_TU8_WORD_FULL \
(TEGRA20_SPDIF_FIFO_ATN_LVL_D_EIGHT_SLOTS << TEGRA20_SPDIF_DATA_FIFO_CSR_TX_ATN_LVL_SHIFT)
#define TEGRA20_SPDIF_DATA_FIFO_CSR_TX_ATN_LVL_TU12_WORD_FULL \
(TEGRA20_SPDIF_FIFO_ATN_LVL_D_TWELVE_SLOTS << TEGRA20_SPDIF_DATA_FIFO_CSR_TX_ATN_LVL_SHIFT)
/* Number of TX DATA.FIFO levels that could be filled. */
#define TEGRA20_SPDIF_DATA_FIFO_CSR_TX_EMPTY_COUNT_SHIFT 0
#define TEGRA20_SPDIF_DATA_FIFO_CSR_TX_EMPTY_COUNT_MASK (0x1f << SPDIF_DATA_FIFO_CSR_TX_EMPTY_COUNT_SHIFT)
/* Fields in TEGRA20_SPDIF_DATA_OUT */
/*
* This register has 5 different formats:
* 16-bit (BIT_MODE=00, PACK=0)
* 20-bit (BIT_MODE=01, PACK=0)
* 24-bit (BIT_MODE=10, PACK=0)
* raw (BIT_MODE=11, PACK=0)
* 16-bit packed (BIT_MODE=00, PACK=1)
*/
#define TEGRA20_SPDIF_DATA_OUT_DATA_16_SHIFT 0
#define TEGRA20_SPDIF_DATA_OUT_DATA_16_MASK (0xffff << TEGRA20_SPDIF_DATA_OUT_DATA_16_SHIFT)
#define TEGRA20_SPDIF_DATA_OUT_DATA_20_SHIFT 0
#define TEGRA20_SPDIF_DATA_OUT_DATA_20_MASK (0xfffff << TEGRA20_SPDIF_DATA_OUT_DATA_20_SHIFT)
#define TEGRA20_SPDIF_DATA_OUT_DATA_24_SHIFT 0
#define TEGRA20_SPDIF_DATA_OUT_DATA_24_MASK (0xffffff << TEGRA20_SPDIF_DATA_OUT_DATA_24_SHIFT)
#define TEGRA20_SPDIF_DATA_OUT_DATA_RAW_P (1 << 31)
#define TEGRA20_SPDIF_DATA_OUT_DATA_RAW_C (1 << 30)
#define TEGRA20_SPDIF_DATA_OUT_DATA_RAW_U (1 << 29)
#define TEGRA20_SPDIF_DATA_OUT_DATA_RAW_V (1 << 28)
#define TEGRA20_SPDIF_DATA_OUT_DATA_RAW_DATA_SHIFT 8
#define TEGRA20_SPDIF_DATA_OUT_DATA_RAW_DATA_MASK (0xfffff << TEGRA20_SPDIF_DATA_OUT_DATA_RAW_DATA_SHIFT)
#define TEGRA20_SPDIF_DATA_OUT_DATA_RAW_AUX_SHIFT 4
#define TEGRA20_SPDIF_DATA_OUT_DATA_RAW_AUX_MASK (0xf << TEGRA20_SPDIF_DATA_OUT_DATA_RAW_AUX_SHIFT)
#define TEGRA20_SPDIF_DATA_OUT_DATA_RAW_PREAMBLE_SHIFT 0
#define TEGRA20_SPDIF_DATA_OUT_DATA_RAW_PREAMBLE_MASK (0xf << TEGRA20_SPDIF_DATA_OUT_DATA_RAW_PREAMBLE_SHIFT)
#define TEGRA20_SPDIF_DATA_OUT_DATA_16_PACKED_RIGHT_SHIFT 16
#define TEGRA20_SPDIF_DATA_OUT_DATA_16_PACKED_RIGHT_MASK (0xffff << TEGRA20_SPDIF_DATA_OUT_DATA_16_PACKED_RIGHT_SHIFT)
#define TEGRA20_SPDIF_DATA_OUT_DATA_16_PACKED_LEFT_SHIFT 0
#define TEGRA20_SPDIF_DATA_OUT_DATA_16_PACKED_LEFT_MASK (0xffff << TEGRA20_SPDIF_DATA_OUT_DATA_16_PACKED_LEFT_SHIFT)
/* Fields in TEGRA20_SPDIF_DATA_IN */
/*
* This register has 5 different formats:
* 16-bit (BIT_MODE=00, PACK=0)
* 20-bit (BIT_MODE=01, PACK=0)
* 24-bit (BIT_MODE=10, PACK=0)
* raw (BIT_MODE=11, PACK=0)
* 16-bit packed (BIT_MODE=00, PACK=1)
*
* Bits 31:24 are common to all modes except 16-bit packed
*/
#define TEGRA20_SPDIF_DATA_IN_DATA_P (1 << 31)
#define TEGRA20_SPDIF_DATA_IN_DATA_C (1 << 30)
#define TEGRA20_SPDIF_DATA_IN_DATA_U (1 << 29)
#define TEGRA20_SPDIF_DATA_IN_DATA_V (1 << 28)
#define TEGRA20_SPDIF_DATA_IN_DATA_PREAMBLE_SHIFT 24
#define TEGRA20_SPDIF_DATA_IN_DATA_PREAMBLE_MASK (0xf << TEGRA20_SPDIF_DATA_IN_DATA_PREAMBLE_SHIFT)
#define TEGRA20_SPDIF_DATA_IN_DATA_16_SHIFT 0
#define TEGRA20_SPDIF_DATA_IN_DATA_16_MASK (0xffff << TEGRA20_SPDIF_DATA_IN_DATA_16_SHIFT)
#define TEGRA20_SPDIF_DATA_IN_DATA_20_SHIFT 0
#define TEGRA20_SPDIF_DATA_IN_DATA_20_MASK (0xfffff << TEGRA20_SPDIF_DATA_IN_DATA_20_SHIFT)
#define TEGRA20_SPDIF_DATA_IN_DATA_24_SHIFT 0
#define TEGRA20_SPDIF_DATA_IN_DATA_24_MASK (0xffffff << TEGRA20_SPDIF_DATA_IN_DATA_24_SHIFT)
#define TEGRA20_SPDIF_DATA_IN_DATA_RAW_DATA_SHIFT 8
#define TEGRA20_SPDIF_DATA_IN_DATA_RAW_DATA_MASK (0xfffff << TEGRA20_SPDIF_DATA_IN_DATA_RAW_DATA_SHIFT)
#define TEGRA20_SPDIF_DATA_IN_DATA_RAW_AUX_SHIFT 4
#define TEGRA20_SPDIF_DATA_IN_DATA_RAW_AUX_MASK (0xf << TEGRA20_SPDIF_DATA_IN_DATA_RAW_AUX_SHIFT)
#define TEGRA20_SPDIF_DATA_IN_DATA_RAW_PREAMBLE_SHIFT 0
#define TEGRA20_SPDIF_DATA_IN_DATA_RAW_PREAMBLE_MASK (0xf << TEGRA20_SPDIF_DATA_IN_DATA_RAW_PREAMBLE_SHIFT)
#define TEGRA20_SPDIF_DATA_IN_DATA_16_PACKED_RIGHT_SHIFT 16
#define TEGRA20_SPDIF_DATA_IN_DATA_16_PACKED_RIGHT_MASK (0xffff << TEGRA20_SPDIF_DATA_IN_DATA_16_PACKED_RIGHT_SHIFT)
#define TEGRA20_SPDIF_DATA_IN_DATA_16_PACKED_LEFT_SHIFT 0
#define TEGRA20_SPDIF_DATA_IN_DATA_16_PACKED_LEFT_MASK (0xffff << TEGRA20_SPDIF_DATA_IN_DATA_16_PACKED_LEFT_SHIFT)
/* Fields in TEGRA20_SPDIF_CH_STA_RX_A */
/* Fields in TEGRA20_SPDIF_CH_STA_RX_B */
/* Fields in TEGRA20_SPDIF_CH_STA_RX_C */
/* Fields in TEGRA20_SPDIF_CH_STA_RX_D */
/* Fields in TEGRA20_SPDIF_CH_STA_RX_E */
/* Fields in TEGRA20_SPDIF_CH_STA_RX_F */
/*
* The 6-word receive channel data page buffer holds a block (192 frames) of
* channel status information. The order of receive is from LSB to MSB
* bit, and from CH_STA_RX_A to CH_STA_RX_F then back to CH_STA_RX_A.
*/
/* Fields in TEGRA20_SPDIF_CH_STA_TX_A */
/* Fields in TEGRA20_SPDIF_CH_STA_TX_B */
/* Fields in TEGRA20_SPDIF_CH_STA_TX_C */
/* Fields in TEGRA20_SPDIF_CH_STA_TX_D */
/* Fields in TEGRA20_SPDIF_CH_STA_TX_E */
/* Fields in TEGRA20_SPDIF_CH_STA_TX_F */
/*
* The 6-word transmit channel data page buffer holds a block (192 frames) of
* channel status information. The order of transmission is from LSB to MSB
* bit, and from CH_STA_TX_A to CH_STA_TX_F then back to CH_STA_TX_A.
*/
/* Fields in TEGRA20_SPDIF_USR_STA_RX_A */
/*
* This 4-word deep FIFO receives user FIFO field information. The order of
* receive is from LSB to MSB bit.
*/
/* Fields in TEGRA20_SPDIF_USR_DAT_TX_A */
/*
* This 4-word deep FIFO transmits user FIFO field information. The order of
* transmission is from LSB to MSB bit.
*/
struct tegra20_spdif {
struct clk *clk_spdif_out;
struct snd_dmaengine_dai_dma_data capture_dma_data;
struct snd_dmaengine_dai_dma_data playback_dma_data;
struct regmap *regmap;
struct reset_control *reset;
};
#endif
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,241 @@
/* SPDX-License-Identifier: GPL-2.0-only
* SPDX-FileCopyrightText: Copyright (c) 2020-2025 NVIDIA CORPORATION & AFFILIATES.
* All rights reserved.
*
* tegra210_admaif.h - Tegra ADMAIF registers
*
*/
#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
#define TEGRA210_ADMAIF_MAX_CHANNEL 16
/* 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
#define TEGRA186_ADMAIF_MAX_CHANNEL 16
/* Tegra264 specific */
#define TEGRA264_ADMAIF_LAST_REG 0x205f
#define TEGRA264_ADMAIF_CHANNEL_COUNT 32
#define TEGRA264_ADMAIF_RX_BASE 0x0
#define TEGRA264_ADMAIF_TX_BASE 0x1000
#define TEGRA264_ADMAIF_GLOBAL_BASE 0x2000
#define TEGRA264_ADMAIF_MAX_CHANNEL 32
/* 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_CIF_REG_DEFAULT 0x00007700
#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_CIF_REG_DEFAULT 0x00007700
#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
/* Default values - Tegra264 */
#define TEGRA264_ADMAIF_CIF_REG_DEFAULT 0x00003f00
#define TEGRA264_ADMAIF_RX1_FIFO_CTRL_REG_DEFAULT 0x00000200
#define TEGRA264_ADMAIF_RX2_FIFO_CTRL_REG_DEFAULT 0x00000203
#define TEGRA264_ADMAIF_RX3_FIFO_CTRL_REG_DEFAULT 0x00000206
#define TEGRA264_ADMAIF_RX4_FIFO_CTRL_REG_DEFAULT 0x00000209
#define TEGRA264_ADMAIF_RX5_FIFO_CTRL_REG_DEFAULT 0x0000020c
#define TEGRA264_ADMAIF_RX6_FIFO_CTRL_REG_DEFAULT 0x0000020f
#define TEGRA264_ADMAIF_RX7_FIFO_CTRL_REG_DEFAULT 0x00000212
#define TEGRA264_ADMAIF_RX8_FIFO_CTRL_REG_DEFAULT 0x00000215
#define TEGRA264_ADMAIF_RX9_FIFO_CTRL_REG_DEFAULT 0x00000218
#define TEGRA264_ADMAIF_RX10_FIFO_CTRL_REG_DEFAULT 0x0000021b
#define TEGRA264_ADMAIF_RX11_FIFO_CTRL_REG_DEFAULT 0x0000021e
#define TEGRA264_ADMAIF_RX12_FIFO_CTRL_REG_DEFAULT 0x00000221
#define TEGRA264_ADMAIF_RX13_FIFO_CTRL_REG_DEFAULT 0x00000224
#define TEGRA264_ADMAIF_RX14_FIFO_CTRL_REG_DEFAULT 0x00000227
#define TEGRA264_ADMAIF_RX15_FIFO_CTRL_REG_DEFAULT 0x0000022a
#define TEGRA264_ADMAIF_RX16_FIFO_CTRL_REG_DEFAULT 0x0000022d
#define TEGRA264_ADMAIF_RX17_FIFO_CTRL_REG_DEFAULT 0x00000230
#define TEGRA264_ADMAIF_RX18_FIFO_CTRL_REG_DEFAULT 0x00000233
#define TEGRA264_ADMAIF_RX19_FIFO_CTRL_REG_DEFAULT 0x00000236
#define TEGRA264_ADMAIF_RX20_FIFO_CTRL_REG_DEFAULT 0x00000239
#define TEGRA264_ADMAIF_RX21_FIFO_CTRL_REG_DEFAULT 0x0000023c
#define TEGRA264_ADMAIF_RX22_FIFO_CTRL_REG_DEFAULT 0x0000023f
#define TEGRA264_ADMAIF_RX23_FIFO_CTRL_REG_DEFAULT 0x00000242
#define TEGRA264_ADMAIF_RX24_FIFO_CTRL_REG_DEFAULT 0x00000245
#define TEGRA264_ADMAIF_RX25_FIFO_CTRL_REG_DEFAULT 0x00000248
#define TEGRA264_ADMAIF_RX26_FIFO_CTRL_REG_DEFAULT 0x0000024b
#define TEGRA264_ADMAIF_RX27_FIFO_CTRL_REG_DEFAULT 0x0000024e
#define TEGRA264_ADMAIF_RX28_FIFO_CTRL_REG_DEFAULT 0x00000251
#define TEGRA264_ADMAIF_RX29_FIFO_CTRL_REG_DEFAULT 0x00000254
#define TEGRA264_ADMAIF_RX30_FIFO_CTRL_REG_DEFAULT 0x00000257
#define TEGRA264_ADMAIF_RX31_FIFO_CTRL_REG_DEFAULT 0x0000025a
#define TEGRA264_ADMAIF_RX32_FIFO_CTRL_REG_DEFAULT 0x0000025d
#define TEGRA264_ADMAIF_TX1_FIFO_CTRL_REG_DEFAULT 0x01800200
#define TEGRA264_ADMAIF_TX2_FIFO_CTRL_REG_DEFAULT 0x01800203
#define TEGRA264_ADMAIF_TX3_FIFO_CTRL_REG_DEFAULT 0x01800206
#define TEGRA264_ADMAIF_TX4_FIFO_CTRL_REG_DEFAULT 0x01800209
#define TEGRA264_ADMAIF_TX5_FIFO_CTRL_REG_DEFAULT 0x0180020c
#define TEGRA264_ADMAIF_TX6_FIFO_CTRL_REG_DEFAULT 0x0180020f
#define TEGRA264_ADMAIF_TX7_FIFO_CTRL_REG_DEFAULT 0x01800212
#define TEGRA264_ADMAIF_TX8_FIFO_CTRL_REG_DEFAULT 0x01800215
#define TEGRA264_ADMAIF_TX9_FIFO_CTRL_REG_DEFAULT 0x01800218
#define TEGRA264_ADMAIF_TX10_FIFO_CTRL_REG_DEFAULT 0x0180021b
#define TEGRA264_ADMAIF_TX11_FIFO_CTRL_REG_DEFAULT 0x0180021e
#define TEGRA264_ADMAIF_TX12_FIFO_CTRL_REG_DEFAULT 0x01800221
#define TEGRA264_ADMAIF_TX13_FIFO_CTRL_REG_DEFAULT 0x01800224
#define TEGRA264_ADMAIF_TX14_FIFO_CTRL_REG_DEFAULT 0x01800227
#define TEGRA264_ADMAIF_TX15_FIFO_CTRL_REG_DEFAULT 0x0180022a
#define TEGRA264_ADMAIF_TX16_FIFO_CTRL_REG_DEFAULT 0x0180022d
#define TEGRA264_ADMAIF_TX17_FIFO_CTRL_REG_DEFAULT 0x01800230
#define TEGRA264_ADMAIF_TX18_FIFO_CTRL_REG_DEFAULT 0x01800233
#define TEGRA264_ADMAIF_TX19_FIFO_CTRL_REG_DEFAULT 0x01800236
#define TEGRA264_ADMAIF_TX20_FIFO_CTRL_REG_DEFAULT 0x01800239
#define TEGRA264_ADMAIF_TX21_FIFO_CTRL_REG_DEFAULT 0x0180023c
#define TEGRA264_ADMAIF_TX22_FIFO_CTRL_REG_DEFAULT 0x0180023f
#define TEGRA264_ADMAIF_TX23_FIFO_CTRL_REG_DEFAULT 0x01800242
#define TEGRA264_ADMAIF_TX24_FIFO_CTRL_REG_DEFAULT 0x01800245
#define TEGRA264_ADMAIF_TX25_FIFO_CTRL_REG_DEFAULT 0x01800248
#define TEGRA264_ADMAIF_TX26_FIFO_CTRL_REG_DEFAULT 0x0180024b
#define TEGRA264_ADMAIF_TX27_FIFO_CTRL_REG_DEFAULT 0x0180024e
#define TEGRA264_ADMAIF_TX28_FIFO_CTRL_REG_DEFAULT 0x01800251
#define TEGRA264_ADMAIF_TX29_FIFO_CTRL_REG_DEFAULT 0x01800254
#define TEGRA264_ADMAIF_TX30_FIFO_CTRL_REG_DEFAULT 0x01800257
#define TEGRA264_ADMAIF_TX31_FIFO_CTRL_REG_DEFAULT 0x0180025a
#define TEGRA264_ADMAIF_TX32_FIFO_CTRL_REG_DEFAULT 0x0180025d
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;
unsigned int max_stream_ch;
};
struct tegra_admaif {
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;
struct tegra_adma_isomgr *adma_isomgr;
struct snd_dmaengine_dai_dma_data capture_dma_data[];
};
#endif
+758
View File
@@ -0,0 +1,758 @@
// SPDX-License-Identifier: GPL-2.0-only
// SPDX-FileCopyrightText: Copyright (c) 2021-2025 NVIDIA CORPORATION & AFFILIATES.
// All rights reserved.
//
// tegra210_adx.c - Tegra210 ADX driver
#include <linux/bits.h>
#include <linux/clk.h>
#include <linux/device.h>
#include <linux/io.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.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include "tegra210_adx.h"
#include "tegra_cif.h"
static const struct reg_default tegra210_adx_reg_defaults[] = {
{ TEGRA210_ADX_RX_INT_MASK, 0x00000001},
{ TEGRA210_ADX_RX_CIF_CTRL, 0x00007000},
{ TEGRA210_ADX_TX_INT_MASK, 0x0000000f },
{ TEGRA210_ADX_TX1_CIF_CTRL, 0x00007000},
{ TEGRA210_ADX_TX2_CIF_CTRL, 0x00007000},
{ TEGRA210_ADX_TX3_CIF_CTRL, 0x00007000},
{ TEGRA210_ADX_TX4_CIF_CTRL, 0x00007000},
{ TEGRA210_ADX_CG, 0x1},
{ TEGRA210_ADX_CFG_RAM_CTRL, 0x00004000},
};
static const struct reg_default tegra264_adx_reg_defaults[] = {
{ TEGRA210_ADX_RX_INT_MASK, 0x00000001},
{ TEGRA210_ADX_RX_CIF_CTRL, 0x00003800},
{ TEGRA210_ADX_TX_INT_MASK, 0x0000000f },
{ TEGRA210_ADX_TX1_CIF_CTRL, 0x00003800},
{ TEGRA210_ADX_TX2_CIF_CTRL, 0x00003800},
{ TEGRA210_ADX_TX3_CIF_CTRL, 0x00003800},
{ TEGRA210_ADX_TX4_CIF_CTRL, 0x00003800},
{ TEGRA210_ADX_CG, 0x1},
{ TEGRA264_ADX_CFG_RAM_CTRL, 0x00004000},
};
static void tegra210_adx_write_map_ram(struct tegra210_adx *adx)
{
const unsigned int bits_per_mask = BITS_PER_TYPE(*adx->byte_mask);
int i;
memset(adx->byte_mask, 0,
adx->soc_data->byte_mask_size * sizeof(*adx->byte_mask));
regmap_write(adx->regmap, TEGRA210_ADX_CFG_RAM_CTRL +
adx->soc_data->cya_offset,
TEGRA210_ADX_CFG_RAM_CTRL_SEQ_ACCESS_EN |
TEGRA210_ADX_CFG_RAM_CTRL_ADDR_INIT_EN |
TEGRA210_ADX_CFG_RAM_CTRL_RW_WRITE);
for (i = 0; i < adx->soc_data->ram_depth; i++) {
u32 word = 0;
int b;
for (b = 0; b < TEGRA_ADX_SLOTS_PER_WORD; b++) {
unsigned int slot = i * TEGRA_ADX_SLOTS_PER_WORD + b;
u16 val = adx->map[slot];
if (val >= 256)
continue;
word |= (u32)val << (b * BITS_PER_BYTE);
adx->byte_mask[slot / bits_per_mask] |=
1U << (slot % bits_per_mask);
}
regmap_write(adx->regmap, TEGRA210_ADX_CFG_RAM_DATA +
adx->soc_data->cya_offset, word);
}
for (i = 0; i < adx->soc_data->byte_mask_size; i++)
regmap_write(adx->regmap,
TEGRA210_ADX_IN_BYTE_EN0 + (i * TEGRA210_ADX_AUDIOCIF_CH_STRIDE),
adx->byte_mask[i]);
}
static int tegra210_adx_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct tegra210_adx *adx = snd_soc_dai_get_drvdata(dai);
unsigned int val;
int err;
/* Ensure if ADX status is disabled */
err = regmap_read_poll_timeout(adx->regmap, TEGRA210_ADX_STATUS,
val, !(val & 0x1), 10, 10000);
if (err < 0) {
dev_err(dai->dev, "failed to stop ADX, err = %d\n", err);
return err;
}
/*
* Soft Reset: Below performs module soft reset which clears
* all FSM logic, flushes flow control of FIFO and resets the
* state register. It also brings module back to disabled
* state (without flushing the data in the pipe).
*/
regmap_update_bits(adx->regmap, TEGRA210_ADX_SOFT_RESET,
TEGRA210_ADX_SOFT_RESET_SOFT_RESET_MASK,
TEGRA210_ADX_SOFT_RESET_SOFT_EN);
err = regmap_read_poll_timeout(adx->regmap, TEGRA210_ADX_SOFT_RESET,
val, !(val & 0x1), 10, 10000);
if (err < 0) {
dev_err(dai->dev, "failed to reset ADX, err = %d\n", err);
return err;
}
return 0;
}
static int tegra210_adx_runtime_suspend(struct device *dev)
{
struct tegra210_adx *adx = dev_get_drvdata(dev);
regcache_cache_only(adx->regmap, true);
regcache_mark_dirty(adx->regmap);
return 0;
}
static int tegra210_adx_runtime_resume(struct device *dev)
{
struct tegra210_adx *adx = dev_get_drvdata(dev);
regcache_cache_only(adx->regmap, false);
regcache_sync(adx->regmap);
tegra210_adx_write_map_ram(adx);
return 0;
}
static int tegra210_adx_set_audio_cif(struct snd_soc_dai *dai,
unsigned int channels,
snd_pcm_format_t format,
unsigned int reg)
{
struct tegra210_adx *adx = snd_soc_dai_get_drvdata(dai);
struct tegra_cif_conf cif_conf;
int audio_bits;
memset(&cif_conf, 0, sizeof(struct tegra_cif_conf));
if (channels < 1 || channels > adx->soc_data->max_ch) {
dev_err(dai->dev, "invalid channels: %u (max %u)\n",
channels, adx->soc_data->max_ch);
return -EINVAL;
}
switch (format) {
case SNDRV_PCM_FORMAT_S8:
audio_bits = TEGRA_ACIF_BITS_8;
break;
case SNDRV_PCM_FORMAT_S16_LE:
audio_bits = TEGRA_ACIF_BITS_16;
break;
case SNDRV_PCM_FORMAT_S24_LE:
case SNDRV_PCM_FORMAT_S32_LE:
audio_bits = TEGRA_ACIF_BITS_32;
break;
default:
dev_err(dai->dev, "unsupported format: %d\n", format);
return -EINVAL;
}
cif_conf.audio_ch = channels;
cif_conf.client_ch = channels;
cif_conf.audio_bits = audio_bits;
cif_conf.client_bits = audio_bits;
if (adx->soc_data->max_ch == 32)
tegra264_set_cif(adx->regmap, reg, &cif_conf);
else
tegra_set_cif(adx->regmap, reg, &cif_conf);
return 0;
}
static int tegra210_adx_out_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
return tegra210_adx_set_audio_cif(dai, params_channels(params),
params_format(params),
TEGRA210_ADX_TX1_CIF_CTRL + ((dai->id - 1) * TEGRA210_ADX_AUDIOCIF_CH_STRIDE));
}
static int tegra210_adx_in_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
return tegra210_adx_set_audio_cif(dai, params_channels(params),
params_format(params),
TEGRA210_ADX_RX_CIF_CTRL);
}
static int tegra210_adx_get_byte_map(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *cmpnt = snd_kcontrol_chip(kcontrol);
struct tegra210_adx *adx = snd_soc_component_get_drvdata(cmpnt);
struct soc_mixer_control *mc =
(struct soc_mixer_control *)kcontrol->private_value;
ucontrol->value.integer.value[0] = adx->map[mc->reg];
return 0;
}
static int tegra210_adx_put_byte_map(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *cmpnt = snd_kcontrol_chip(kcontrol);
struct tegra210_adx *adx = snd_soc_component_get_drvdata(cmpnt);
struct soc_mixer_control *mc =
(struct soc_mixer_control *)kcontrol->private_value;
unsigned int value = ucontrol->value.integer.value[0];
/*
* Match the previous behaviour: any value outside [0, 255] is
* treated as the "disabled" sentinel (256). Negative values from
* userspace fold in through the unsigned cast and are caught here.
*/
if (value > 255)
value = 256;
if (adx->map[mc->reg] == value)
return 0;
adx->map[mc->reg] = value;
return 1;
}
static const struct snd_soc_dai_ops tegra210_adx_in_dai_ops = {
.hw_params = tegra210_adx_in_hw_params,
.startup = tegra210_adx_startup,
};
static const struct snd_soc_dai_ops tegra210_adx_out_dai_ops = {
.hw_params = tegra210_adx_out_hw_params,
};
#define IN_DAI \
{ \
.name = "ADX-RX-CIF", \
.playback = { \
.stream_name = "RX-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_S24_LE | \
SNDRV_PCM_FMTBIT_S32_LE, \
}, \
.capture = { \
.stream_name = "RX-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_S24_LE | \
SNDRV_PCM_FMTBIT_S32_LE, \
}, \
.ops = &tegra210_adx_in_dai_ops, \
}
#define OUT_DAI(id) \
{ \
.name = "ADX-TX" #id "-CIF", \
.playback = { \
.stream_name = "TX" #id "-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_S24_LE | \
SNDRV_PCM_FMTBIT_S32_LE, \
}, \
.capture = { \
.stream_name = "TX" #id "-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_S24_LE | \
SNDRV_PCM_FMTBIT_S32_LE, \
}, \
.ops = &tegra210_adx_out_dai_ops, \
}
static struct snd_soc_dai_driver tegra210_adx_dais[] = {
IN_DAI,
OUT_DAI(1),
OUT_DAI(2),
OUT_DAI(3),
OUT_DAI(4),
};
static const struct snd_soc_dapm_widget tegra210_adx_widgets[] = {
SND_SOC_DAPM_AIF_IN("RX", NULL, 0, TEGRA210_ADX_ENABLE,
TEGRA210_ADX_ENABLE_SHIFT, 0),
SND_SOC_DAPM_AIF_OUT("TX1", NULL, 0, TEGRA210_ADX_CTRL, 0, 0),
SND_SOC_DAPM_AIF_OUT("TX2", NULL, 0, TEGRA210_ADX_CTRL, 1, 0),
SND_SOC_DAPM_AIF_OUT("TX3", NULL, 0, TEGRA210_ADX_CTRL, 2, 0),
SND_SOC_DAPM_AIF_OUT("TX4", NULL, 0, TEGRA210_ADX_CTRL, 3, 0),
};
#define STREAM_ROUTES(id, sname) \
{ "XBAR-" sname, NULL, "XBAR-TX" }, \
{ "RX-CIF-" sname, NULL, "XBAR-" sname }, \
{ "RX", NULL, "RX-CIF-" sname }, \
{ "TX" #id, NULL, "RX" }, \
{ "TX" #id "-CIF-" sname, NULL, "TX" #id }, \
{ "TX" #id " XBAR-" sname, NULL, "TX" #id "-CIF-" sname }, \
{ "TX" #id " XBAR-RX", NULL, "TX" #id " XBAR-" sname }
#define ADX_ROUTES(id) \
STREAM_ROUTES(id, "Playback"), \
STREAM_ROUTES(id, "Capture")
#define STREAM_ROUTES(id, sname) \
{ "XBAR-" sname, NULL, "XBAR-TX" }, \
{ "RX-CIF-" sname, NULL, "XBAR-" sname }, \
{ "RX", NULL, "RX-CIF-" sname }, \
{ "TX" #id, NULL, "RX" }, \
{ "TX" #id "-CIF-" sname, NULL, "TX" #id }, \
{ "TX" #id " XBAR-" sname, NULL, "TX" #id "-CIF-" sname }, \
{ "TX" #id " XBAR-RX", NULL, "TX" #id " XBAR-" sname }
#define ADX_ROUTES(id) \
STREAM_ROUTES(id, "Playback"), \
STREAM_ROUTES(id, "Capture")
static const struct snd_soc_dapm_route tegra210_adx_routes[] = {
ADX_ROUTES(1),
ADX_ROUTES(2),
ADX_ROUTES(3),
ADX_ROUTES(4),
};
#define TEGRA210_ADX_BYTE_MAP_CTRL(reg) \
SOC_SINGLE_EXT("Byte Map " #reg, reg, 0, 256, 0, \
tegra210_adx_get_byte_map, \
tegra210_adx_put_byte_map)
static struct snd_kcontrol_new tegra210_adx_controls[] = {
TEGRA210_ADX_BYTE_MAP_CTRL(0),
TEGRA210_ADX_BYTE_MAP_CTRL(1),
TEGRA210_ADX_BYTE_MAP_CTRL(2),
TEGRA210_ADX_BYTE_MAP_CTRL(3),
TEGRA210_ADX_BYTE_MAP_CTRL(4),
TEGRA210_ADX_BYTE_MAP_CTRL(5),
TEGRA210_ADX_BYTE_MAP_CTRL(6),
TEGRA210_ADX_BYTE_MAP_CTRL(7),
TEGRA210_ADX_BYTE_MAP_CTRL(8),
TEGRA210_ADX_BYTE_MAP_CTRL(9),
TEGRA210_ADX_BYTE_MAP_CTRL(10),
TEGRA210_ADX_BYTE_MAP_CTRL(11),
TEGRA210_ADX_BYTE_MAP_CTRL(12),
TEGRA210_ADX_BYTE_MAP_CTRL(13),
TEGRA210_ADX_BYTE_MAP_CTRL(14),
TEGRA210_ADX_BYTE_MAP_CTRL(15),
TEGRA210_ADX_BYTE_MAP_CTRL(16),
TEGRA210_ADX_BYTE_MAP_CTRL(17),
TEGRA210_ADX_BYTE_MAP_CTRL(18),
TEGRA210_ADX_BYTE_MAP_CTRL(19),
TEGRA210_ADX_BYTE_MAP_CTRL(20),
TEGRA210_ADX_BYTE_MAP_CTRL(21),
TEGRA210_ADX_BYTE_MAP_CTRL(22),
TEGRA210_ADX_BYTE_MAP_CTRL(23),
TEGRA210_ADX_BYTE_MAP_CTRL(24),
TEGRA210_ADX_BYTE_MAP_CTRL(25),
TEGRA210_ADX_BYTE_MAP_CTRL(26),
TEGRA210_ADX_BYTE_MAP_CTRL(27),
TEGRA210_ADX_BYTE_MAP_CTRL(28),
TEGRA210_ADX_BYTE_MAP_CTRL(29),
TEGRA210_ADX_BYTE_MAP_CTRL(30),
TEGRA210_ADX_BYTE_MAP_CTRL(31),
TEGRA210_ADX_BYTE_MAP_CTRL(32),
TEGRA210_ADX_BYTE_MAP_CTRL(33),
TEGRA210_ADX_BYTE_MAP_CTRL(34),
TEGRA210_ADX_BYTE_MAP_CTRL(35),
TEGRA210_ADX_BYTE_MAP_CTRL(36),
TEGRA210_ADX_BYTE_MAP_CTRL(37),
TEGRA210_ADX_BYTE_MAP_CTRL(38),
TEGRA210_ADX_BYTE_MAP_CTRL(39),
TEGRA210_ADX_BYTE_MAP_CTRL(40),
TEGRA210_ADX_BYTE_MAP_CTRL(41),
TEGRA210_ADX_BYTE_MAP_CTRL(42),
TEGRA210_ADX_BYTE_MAP_CTRL(43),
TEGRA210_ADX_BYTE_MAP_CTRL(44),
TEGRA210_ADX_BYTE_MAP_CTRL(45),
TEGRA210_ADX_BYTE_MAP_CTRL(46),
TEGRA210_ADX_BYTE_MAP_CTRL(47),
TEGRA210_ADX_BYTE_MAP_CTRL(48),
TEGRA210_ADX_BYTE_MAP_CTRL(49),
TEGRA210_ADX_BYTE_MAP_CTRL(50),
TEGRA210_ADX_BYTE_MAP_CTRL(51),
TEGRA210_ADX_BYTE_MAP_CTRL(52),
TEGRA210_ADX_BYTE_MAP_CTRL(53),
TEGRA210_ADX_BYTE_MAP_CTRL(54),
TEGRA210_ADX_BYTE_MAP_CTRL(55),
TEGRA210_ADX_BYTE_MAP_CTRL(56),
TEGRA210_ADX_BYTE_MAP_CTRL(57),
TEGRA210_ADX_BYTE_MAP_CTRL(58),
TEGRA210_ADX_BYTE_MAP_CTRL(59),
TEGRA210_ADX_BYTE_MAP_CTRL(60),
TEGRA210_ADX_BYTE_MAP_CTRL(61),
TEGRA210_ADX_BYTE_MAP_CTRL(62),
TEGRA210_ADX_BYTE_MAP_CTRL(63),
};
static struct snd_kcontrol_new tegra264_adx_controls[] = {
TEGRA210_ADX_BYTE_MAP_CTRL(64),
TEGRA210_ADX_BYTE_MAP_CTRL(65),
TEGRA210_ADX_BYTE_MAP_CTRL(66),
TEGRA210_ADX_BYTE_MAP_CTRL(67),
TEGRA210_ADX_BYTE_MAP_CTRL(68),
TEGRA210_ADX_BYTE_MAP_CTRL(69),
TEGRA210_ADX_BYTE_MAP_CTRL(70),
TEGRA210_ADX_BYTE_MAP_CTRL(71),
TEGRA210_ADX_BYTE_MAP_CTRL(72),
TEGRA210_ADX_BYTE_MAP_CTRL(73),
TEGRA210_ADX_BYTE_MAP_CTRL(74),
TEGRA210_ADX_BYTE_MAP_CTRL(75),
TEGRA210_ADX_BYTE_MAP_CTRL(76),
TEGRA210_ADX_BYTE_MAP_CTRL(77),
TEGRA210_ADX_BYTE_MAP_CTRL(78),
TEGRA210_ADX_BYTE_MAP_CTRL(79),
TEGRA210_ADX_BYTE_MAP_CTRL(80),
TEGRA210_ADX_BYTE_MAP_CTRL(81),
TEGRA210_ADX_BYTE_MAP_CTRL(82),
TEGRA210_ADX_BYTE_MAP_CTRL(83),
TEGRA210_ADX_BYTE_MAP_CTRL(84),
TEGRA210_ADX_BYTE_MAP_CTRL(85),
TEGRA210_ADX_BYTE_MAP_CTRL(86),
TEGRA210_ADX_BYTE_MAP_CTRL(87),
TEGRA210_ADX_BYTE_MAP_CTRL(88),
TEGRA210_ADX_BYTE_MAP_CTRL(89),
TEGRA210_ADX_BYTE_MAP_CTRL(90),
TEGRA210_ADX_BYTE_MAP_CTRL(91),
TEGRA210_ADX_BYTE_MAP_CTRL(92),
TEGRA210_ADX_BYTE_MAP_CTRL(93),
TEGRA210_ADX_BYTE_MAP_CTRL(94),
TEGRA210_ADX_BYTE_MAP_CTRL(95),
TEGRA210_ADX_BYTE_MAP_CTRL(96),
TEGRA210_ADX_BYTE_MAP_CTRL(97),
TEGRA210_ADX_BYTE_MAP_CTRL(98),
TEGRA210_ADX_BYTE_MAP_CTRL(99),
TEGRA210_ADX_BYTE_MAP_CTRL(100),
TEGRA210_ADX_BYTE_MAP_CTRL(101),
TEGRA210_ADX_BYTE_MAP_CTRL(102),
TEGRA210_ADX_BYTE_MAP_CTRL(103),
TEGRA210_ADX_BYTE_MAP_CTRL(104),
TEGRA210_ADX_BYTE_MAP_CTRL(105),
TEGRA210_ADX_BYTE_MAP_CTRL(106),
TEGRA210_ADX_BYTE_MAP_CTRL(107),
TEGRA210_ADX_BYTE_MAP_CTRL(108),
TEGRA210_ADX_BYTE_MAP_CTRL(109),
TEGRA210_ADX_BYTE_MAP_CTRL(110),
TEGRA210_ADX_BYTE_MAP_CTRL(111),
TEGRA210_ADX_BYTE_MAP_CTRL(112),
TEGRA210_ADX_BYTE_MAP_CTRL(113),
TEGRA210_ADX_BYTE_MAP_CTRL(114),
TEGRA210_ADX_BYTE_MAP_CTRL(115),
TEGRA210_ADX_BYTE_MAP_CTRL(116),
TEGRA210_ADX_BYTE_MAP_CTRL(117),
TEGRA210_ADX_BYTE_MAP_CTRL(118),
TEGRA210_ADX_BYTE_MAP_CTRL(119),
TEGRA210_ADX_BYTE_MAP_CTRL(120),
TEGRA210_ADX_BYTE_MAP_CTRL(121),
TEGRA210_ADX_BYTE_MAP_CTRL(122),
TEGRA210_ADX_BYTE_MAP_CTRL(123),
TEGRA210_ADX_BYTE_MAP_CTRL(124),
TEGRA210_ADX_BYTE_MAP_CTRL(125),
TEGRA210_ADX_BYTE_MAP_CTRL(126),
TEGRA210_ADX_BYTE_MAP_CTRL(127),
};
static int tegra210_adx_component_probe(struct snd_soc_component *component)
{
struct tegra210_adx *adx = snd_soc_component_get_drvdata(component);
int err = 0;
if (adx->soc_data->num_controls) {
err = snd_soc_add_component_controls(component, adx->soc_data->controls,
adx->soc_data->num_controls);
if (err)
dev_err(component->dev, "can't add ADX controls, err: %d\n", err);
}
return err;
}
static const struct snd_soc_component_driver tegra210_adx_cmpnt = {
.probe = tegra210_adx_component_probe,
.dapm_widgets = tegra210_adx_widgets,
.num_dapm_widgets = ARRAY_SIZE(tegra210_adx_widgets),
.dapm_routes = tegra210_adx_routes,
.num_dapm_routes = ARRAY_SIZE(tegra210_adx_routes),
.controls = tegra210_adx_controls,
.num_controls = ARRAY_SIZE(tegra210_adx_controls),
};
static bool tegra210_adx_wr_reg(struct device *dev,
unsigned int reg)
{
switch (reg) {
case TEGRA210_ADX_TX_INT_MASK ... TEGRA210_ADX_TX4_CIF_CTRL:
case TEGRA210_ADX_RX_INT_MASK ... TEGRA210_ADX_RX_CIF_CTRL:
case TEGRA210_ADX_ENABLE ... TEGRA210_ADX_CG:
case TEGRA210_ADX_CTRL ... TEGRA210_ADX_IN_BYTE_EN1:
case TEGRA210_ADX_CFG_RAM_CTRL ... TEGRA210_ADX_CFG_RAM_DATA:
return true;
default:
return false;
}
}
static bool tegra210_adx_rd_reg(struct device *dev,
unsigned int reg)
{
switch (reg) {
case TEGRA210_ADX_RX_STATUS ... TEGRA210_ADX_CFG_RAM_DATA:
return true;
default:
return false;
}
}
static bool tegra210_adx_volatile_reg(struct device *dev,
unsigned int reg)
{
switch (reg) {
case TEGRA210_ADX_RX_STATUS:
case TEGRA210_ADX_RX_INT_STATUS:
case TEGRA210_ADX_RX_INT_SET:
case TEGRA210_ADX_TX_STATUS:
case TEGRA210_ADX_TX_INT_STATUS:
case TEGRA210_ADX_TX_INT_SET:
case TEGRA210_ADX_SOFT_RESET:
case TEGRA210_ADX_STATUS:
case TEGRA210_ADX_INT_STATUS:
case TEGRA210_ADX_CFG_RAM_CTRL:
case TEGRA210_ADX_CFG_RAM_DATA:
return true;
default:
break;
}
return false;
}
static bool tegra264_adx_wr_reg(struct device *dev,
unsigned int reg)
{
switch (reg) {
case TEGRA210_ADX_TX_INT_MASK ... TEGRA210_ADX_TX4_CIF_CTRL:
case TEGRA210_ADX_RX_INT_MASK ... TEGRA210_ADX_RX_CIF_CTRL:
case TEGRA210_ADX_ENABLE ... TEGRA210_ADX_CG:
case TEGRA210_ADX_CTRL ... TEGRA264_ADX_CYA:
case TEGRA264_ADX_CFG_RAM_CTRL ... TEGRA264_ADX_CFG_RAM_DATA:
return true;
default:
return false;
}
}
static bool tegra264_adx_rd_reg(struct device *dev,
unsigned int reg)
{
switch (reg) {
case TEGRA210_ADX_RX_STATUS ... TEGRA210_ADX_RX_CIF_CTRL:
case TEGRA210_ADX_TX_STATUS ... TEGRA210_ADX_TX4_CIF_CTRL:
case TEGRA210_ADX_ENABLE ... TEGRA210_ADX_INT_STATUS:
case TEGRA210_ADX_CTRL ... TEGRA264_ADX_CFG_RAM_DATA:
return true;
default:
return false;
}
}
static bool tegra264_adx_volatile_reg(struct device *dev,
unsigned int reg)
{
switch (reg) {
case TEGRA210_ADX_RX_STATUS:
case TEGRA210_ADX_RX_INT_STATUS:
case TEGRA210_ADX_RX_INT_SET:
case TEGRA210_ADX_TX_STATUS:
case TEGRA210_ADX_TX_INT_STATUS:
case TEGRA210_ADX_TX_INT_SET:
case TEGRA210_ADX_SOFT_RESET:
case TEGRA210_ADX_STATUS:
case TEGRA210_ADX_INT_STATUS:
case TEGRA264_ADX_CFG_RAM_CTRL:
case TEGRA264_ADX_CFG_RAM_DATA:
return true;
default:
break;
}
return false;
}
static const struct regmap_config tegra210_adx_regmap_config = {
.reg_bits = 32,
.reg_stride = 4,
.val_bits = 32,
.max_register = TEGRA210_ADX_CFG_RAM_DATA,
.writeable_reg = tegra210_adx_wr_reg,
.readable_reg = tegra210_adx_rd_reg,
.volatile_reg = tegra210_adx_volatile_reg,
.reg_defaults = tegra210_adx_reg_defaults,
.num_reg_defaults = ARRAY_SIZE(tegra210_adx_reg_defaults),
.reg_default_cb = regmap_default_zero_cb,
.cache_type = REGCACHE_FLAT,
};
static const struct regmap_config tegra264_adx_regmap_config = {
.reg_bits = 32,
.reg_stride = 4,
.val_bits = 32,
.max_register = TEGRA264_ADX_CFG_RAM_DATA,
.writeable_reg = tegra264_adx_wr_reg,
.readable_reg = tegra264_adx_rd_reg,
.volatile_reg = tegra264_adx_volatile_reg,
.reg_defaults = tegra264_adx_reg_defaults,
.num_reg_defaults = ARRAY_SIZE(tegra264_adx_reg_defaults),
.reg_default_cb = regmap_default_zero_cb,
.cache_type = REGCACHE_FLAT,
};
static const struct tegra210_adx_soc_data soc_data_tegra210 = {
.regmap_conf = &tegra210_adx_regmap_config,
.max_ch = TEGRA210_ADX_MAX_CHANNEL,
.ram_depth = TEGRA210_ADX_RAM_DEPTH,
.byte_mask_size = TEGRA210_ADX_BYTE_MASK_COUNT,
.cya_offset = TEGRA210_ADX_CYA_OFFSET,
};
static const struct tegra210_adx_soc_data soc_data_tegra264 = {
.regmap_conf = &tegra264_adx_regmap_config,
.max_ch = TEGRA264_ADX_MAX_CHANNEL,
.ram_depth = TEGRA264_ADX_RAM_DEPTH,
.byte_mask_size = TEGRA264_ADX_BYTE_MASK_COUNT,
.cya_offset = TEGRA264_ADX_CYA_OFFSET,
.controls = tegra264_adx_controls,
.num_controls = ARRAY_SIZE(tegra264_adx_controls),
};
static const struct of_device_id tegra210_adx_of_match[] = {
{ .compatible = "nvidia,tegra210-adx", .data = &soc_data_tegra210 },
{ .compatible = "nvidia,tegra264-adx", .data = &soc_data_tegra264 },
{},
};
MODULE_DEVICE_TABLE(of, tegra210_adx_of_match);
static int tegra210_adx_platform_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct tegra210_adx *adx;
const struct tegra210_adx_soc_data *soc_data;
void __iomem *regs;
int err, i;
adx = devm_kzalloc(dev, sizeof(*adx), GFP_KERNEL);
if (!adx)
return -ENOMEM;
soc_data = of_device_get_match_data(dev);
adx->soc_data = soc_data;
dev_set_drvdata(dev, adx);
regs = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(regs))
return PTR_ERR(regs);
adx->regmap = devm_regmap_init_mmio(dev, regs,
soc_data->regmap_conf);
if (IS_ERR(adx->regmap))
return dev_err_probe(dev, PTR_ERR(adx->regmap),
"regmap init failed\n");
regcache_cache_only(adx->regmap, true);
adx->map = devm_kcalloc(dev,
soc_data->ram_depth * TEGRA_ADX_SLOTS_PER_WORD,
sizeof(*adx->map), GFP_KERNEL);
if (!adx->map)
return -ENOMEM;
adx->byte_mask = devm_kcalloc(dev, soc_data->byte_mask_size,
sizeof(*adx->byte_mask), GFP_KERNEL);
if (!adx->byte_mask)
return -ENOMEM;
/* Initialise all byte map slots as disabled (value 256). */
for (i = 0; i < soc_data->ram_depth * TEGRA_ADX_SLOTS_PER_WORD; i++)
adx->map[i] = 256;
tegra210_adx_dais[TEGRA_ADX_IN_DAI_ID].playback.channels_max =
adx->soc_data->max_ch;
err = devm_snd_soc_register_component(dev, &tegra210_adx_cmpnt,
tegra210_adx_dais,
ARRAY_SIZE(tegra210_adx_dais));
if (err)
return dev_err_probe(dev, err,
"can't register ADX component\n");
pm_runtime_enable(dev);
return 0;
}
static void tegra210_adx_platform_remove(struct platform_device *pdev)
{
pm_runtime_disable(&pdev->dev);
}
static const struct dev_pm_ops tegra210_adx_pm_ops = {
RUNTIME_PM_OPS(tegra210_adx_runtime_suspend,
tegra210_adx_runtime_resume, NULL)
SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume)
};
static struct platform_driver tegra210_adx_driver = {
.driver = {
.name = "tegra210-adx",
.of_match_table = tegra210_adx_of_match,
.pm = pm_ptr(&tegra210_adx_pm_ops),
},
.probe = tegra210_adx_platform_probe,
.remove = tegra210_adx_platform_remove,
};
module_platform_driver(tegra210_adx_driver);
MODULE_AUTHOR("Arun Shamanna Lakshmi <aruns@nvidia.com>");
MODULE_DESCRIPTION("Tegra210 ADX ASoC driver");
MODULE_LICENSE("GPL v2");
@@ -0,0 +1,99 @@
/* SPDX-License-Identifier: GPL-2.0-only
* SPDX-FileCopyrightText: Copyright (c) 2021-2025 NVIDIA CORPORATION. All rights reserved.
*
* tegra210_adx.h - Definitions for Tegra210 ADX driver
*
*/
#ifndef __TEGRA210_ADX_H__
#define __TEGRA210_ADX_H__
#include <linux/types.h>
/* Register offsets from TEGRA210_ADX*_BASE */
#define TEGRA210_ADX_RX_STATUS 0x0c
#define TEGRA210_ADX_RX_INT_STATUS 0x10
#define TEGRA210_ADX_RX_INT_MASK 0x14
#define TEGRA210_ADX_RX_INT_SET 0x18
#define TEGRA210_ADX_RX_INT_CLEAR 0x1c
#define TEGRA210_ADX_RX_CIF_CTRL 0x20
#define TEGRA210_ADX_TX_STATUS 0x4c
#define TEGRA210_ADX_TX_INT_STATUS 0x50
#define TEGRA210_ADX_TX_INT_MASK 0x54
#define TEGRA210_ADX_TX_INT_SET 0x58
#define TEGRA210_ADX_TX_INT_CLEAR 0x5c
#define TEGRA210_ADX_TX1_CIF_CTRL 0x60
#define TEGRA210_ADX_TX2_CIF_CTRL 0x64
#define TEGRA210_ADX_TX3_CIF_CTRL 0x68
#define TEGRA210_ADX_TX4_CIF_CTRL 0x6c
#define TEGRA210_ADX_ENABLE 0x80
#define TEGRA210_ADX_SOFT_RESET 0x84
#define TEGRA210_ADX_CG 0x88
#define TEGRA210_ADX_STATUS 0x8c
#define TEGRA210_ADX_INT_STATUS 0x90
#define TEGRA210_ADX_CTRL 0xa4
#define TEGRA210_ADX_IN_BYTE_EN0 0xa8
#define TEGRA210_ADX_IN_BYTE_EN1 0xac
#define TEGRA210_ADX_CFG_RAM_CTRL 0xb8
#define TEGRA210_ADX_CFG_RAM_DATA 0xbc
#define TEGRA264_ADX_CYA 0xb8
#define TEGRA264_ADX_CFG_RAM_CTRL 0xc0
#define TEGRA264_ADX_CFG_RAM_DATA 0xc4
/* Fields in TEGRA210_ADX_ENABLE */
#define TEGRA210_ADX_ENABLE_SHIFT 0
/* Fields in TEGRA210_ADX_CFG_RAM_CTRL */
#define TEGRA210_ADX_CFG_RAM_CTRL_RAM_ADDR_SHIFT 0
#define TEGRA210_ADX_CFG_RAM_CTRL_RW_SHIFT 14
#define TEGRA210_ADX_CFG_RAM_CTRL_RW_WRITE (1 << TEGRA210_ADX_CFG_RAM_CTRL_RW_SHIFT)
#define TEGRA210_ADX_CFG_RAM_CTRL_ADDR_INIT_EN_SHIFT 13
#define TEGRA210_ADX_CFG_RAM_CTRL_ADDR_INIT_EN (1 << TEGRA210_ADX_CFG_RAM_CTRL_ADDR_INIT_EN_SHIFT)
#define TEGRA210_ADX_CFG_RAM_CTRL_SEQ_ACCESS_EN_SHIFT 12
#define TEGRA210_ADX_CFG_RAM_CTRL_SEQ_ACCESS_EN (1 << TEGRA210_ADX_CFG_RAM_CTRL_SEQ_ACCESS_EN_SHIFT)
/* Fields in TEGRA210_ADX_SOFT_RESET */
#define TEGRA210_ADX_SOFT_RESET_SOFT_RESET_SHIFT 0
#define TEGRA210_ADX_SOFT_RESET_SOFT_RESET_MASK (1 << TEGRA210_ADX_SOFT_RESET_SOFT_RESET_SHIFT)
#define TEGRA210_ADX_SOFT_RESET_SOFT_EN (1 << TEGRA210_ADX_SOFT_RESET_SOFT_RESET_SHIFT)
#define TEGRA210_ADX_SOFT_RESET_SOFT_DEFAULT (0 << TEGRA210_ADX_SOFT_RESET_SOFT_RESET_SHIFT)
#define TEGRA210_ADX_AUDIOCIF_CH_STRIDE 4
#define TEGRA_ADX_SLOTS_PER_WORD 4
#define TEGRA210_ADX_RAM_DEPTH 16
#define TEGRA210_ADX_MAP_STREAM_NUMBER_SHIFT 6
#define TEGRA210_ADX_MAP_WORD_NUMBER_SHIFT 2
#define TEGRA210_ADX_MAP_BYTE_NUMBER_SHIFT 0
#define TEGRA210_ADX_BYTE_MASK_COUNT 2
#define TEGRA210_ADX_MAX_CHANNEL 16
#define TEGRA210_ADX_CYA_OFFSET 0
#define TEGRA264_ADX_RAM_DEPTH 32
#define TEGRA264_ADX_BYTE_MASK_COUNT 4
#define TEGRA264_ADX_MAX_CHANNEL 32
#define TEGRA264_ADX_CYA_OFFSET 8
#define TEGRA_ADX_IN_DAI_ID 4
struct tegra210_adx_soc_data {
const struct regmap_config *regmap_conf;
const struct snd_kcontrol_new *controls;
unsigned int num_controls;
unsigned int max_ch;
unsigned int ram_depth;
unsigned int byte_mask_size;
unsigned int cya_offset;
};
struct tegra210_adx {
struct regmap *regmap;
unsigned int *byte_mask;
u16 *map;
const struct tegra210_adx_soc_data *soc_data;
};
#endif
File diff suppressed because it is too large Load Diff
+201
View File
@@ -0,0 +1,201 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* tegra210_ahub.h - TEGRA210 AHUB
*
* Copyright (c) 2020-2025, 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
/* Tegra264 specific */
#define TEGRA264_XBAR_PART1_RX 0x1000
#define TEGRA264_XBAR_PART2_RX 0x2000
#define TEGRA264_XBAR_PART3_RX 0x3000
#define TEGRA264_XBAR_PART4_RX 0x4000
#define TEGRA264_XBAR_PART0_ADX6_RX1 0x224
#define TEGRA264_XBAR_AUDIO_RX_COUNT ((TEGRA264_XBAR_PART0_ADX6_RX1 / 4) + 1)
#define TEGRA264_XBAR_REG_MASK_0 0xfffffff
#define TEGRA264_XBAR_REG_MASK_1 0x3f013f1f
#define TEGRA264_XBAR_REG_MASK_2 0xff3c0301
#define TEGRA264_XBAR_REG_MASK_3 0x3f00ffff
#define TEGRA264_XBAR_REG_MASK_4 0x7fff9f
#define TEGRA264_XBAR_UPDATE_MAX_REG 5
#define TEGRA264_AXBAR_ADMAIF_RX1 0x0
#define TEGRA264_AXBAR_SFC4_RX1 0x6c
#define TEGRA264_AXBAR_MIXER1_RX1 0x80
#define TEGRA264_AXBAR_MIXER1_RX10 0xa4
#define TEGRA264_AXBAR_DSPK1_RX1 0xc0
#define TEGRA264_AXBAR_OPE1_RX1 0x100
#define TEGRA264_AXBAR_MVC1_RX1 0x110
#define TEGRA264_AXBAR_MVC2_RX1 0x114
#define TEGRA264_AXBAR_AMX1_RX1 0x120
#define TEGRA264_AXBAR_AMX3_RX4 0x14c
#define TEGRA264_AXBAR_ADX1_RX1 0x160
#define TEGRA264_AXBAR_ASRC1_RX7 0x1a8
#define TEGRA264_AXBAR_ADMAIF_RX21 0x1d0
#define TEGRA264_AXBAR_ADX6_RX1 0x224
#define TEGRA_XBAR_UPDATE_MAX_REG (TEGRA264_XBAR_UPDATE_MAX_REG)
#define TEGRA264_MAX_REGISTER_ADDR (TEGRA264_XBAR_PART4_RX + \
(TEGRA210_XBAR_RX_STRIDE * (TEGRA264_XBAR_AUDIO_RX_COUNT - 1)))
#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)))
/* AXBAR register offsets */
#define TEGRA186_AXBAR_PART_0_AMX1_RX1_0 0x120
#define TEGRA186_AXBAR_PART_0_AMX3_RX4_0 0x14c
#define TEGRA186_AXBAR_PART_0_ASRC1_RX7_0 0x1a8
#define TEGRA186_AXBAR_PART_0_DSPK1_RX1_0 0xc0
#define TEGRA186_AXBAR_PART_0_DSPK2_RX1_0 0xc4
#define TEGRA186_AXBAR_PART_0_I2S6_RX1_0 0x54
#define TEGRA186_AXBAR_PART_0_MVC1_RX1_0 0x110
#define TEGRA186_AXBAR_PART_0_MVC2_RX1_0 0x114
#define TEGRA210_AXBAR_PART_0_ADMAIF_RX10_0 0x24
#define TEGRA210_AXBAR_PART_0_ADMAIF_RX1_0 0x0
#define TEGRA210_AXBAR_PART_0_ADX1_RX1_0 0x160
#define TEGRA210_AXBAR_PART_0_ADX2_RX1_0 0x164
#define TEGRA210_AXBAR_PART_0_AFC1_RX1_0 0xd0
#define TEGRA210_AXBAR_PART_0_AFC6_RX1_0 0xe4
#define TEGRA210_AXBAR_PART_0_AMX1_RX1_0 0x140
#define TEGRA210_AXBAR_PART_0_I2S1_RX1_0 0x40
#define TEGRA210_AXBAR_PART_0_I2S5_RX1_0 0x50
#define TEGRA210_AXBAR_PART_0_MIXER1_RX10_0 0xa4
#define TEGRA210_AXBAR_PART_0_MIXER1_RX1_0 0x80
#define TEGRA210_AXBAR_PART_0_MVC1_RX1_0 0x120
#define TEGRA210_AXBAR_PART_0_MVC2_RX1_0 0x124
#define TEGRA210_AXBAR_PART_0_OPE1_RX1_0 0x100
#define TEGRA210_AXBAR_PART_0_OPE2_RX1_0 0x104
#define TEGRA210_AXBAR_PART_0_SFC1_RX1_0 0x60
#define TEGRA210_AXBAR_PART_0_SFC4_RX1_0 0x6c
#define TEGRA210_AXBAR_PART_0_SPDIF1_RX1_0 0xc0
#define TEGRA210_AXBAR_PART_0_SPDIF1_RX2_0 0xc4
#define TEGRA210_AXBAR_PART_0_SPKPROT1_RX1_0 0x110
#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 MUX_ENUM_CTRL_DECL_234(ename, id) MUX_ENUM_CTRL_DECL_186(ename, id)
#define MUX_ENUM_CTRL_DECL_264(ename, id) \
SOC_VALUE_ENUM_WIDE_DECL(ename##_enum, MUX_REG(id), 0, \
tegra264_ahub_mux_texts, \
tegra264_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 = 32, \
.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 = 32, \
.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[TEGRA_XBAR_UPDATE_MAX_REG];
unsigned int reg_count;
unsigned int num_dais;
unsigned int xbar_part_size;
};
struct tegra_ahub {
const struct tegra_ahub_soc_data *soc_data;
struct regmap *regmap;
struct clk *clk;
};
#endif
+807
View File
@@ -0,0 +1,807 @@
// SPDX-License-Identifier: GPL-2.0-only
// SPDX-FileCopyrightText: Copyright (c) 2021-2025 NVIDIA CORPORATION & AFFILIATES.
// All rights reserved.
//
// tegra210_amx.c - Tegra210 AMX driver
#include <linux/bits.h>
#include <linux/clk.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include "tegra210_amx.h"
#include "tegra_cif.h"
/*
* The counter is in terms of AHUB clock cycles. If a frame is not
* received within these clock cycles, the AMX input channel gets
* automatically disabled. For now the counter is calculated as a
* function of sample rate (8 kHz) and AHUB clock (49.152 MHz).
* If later an accurate number is needed, the counter needs to be
* calculated at runtime.
*
* count = ahub_clk / sample_rate
*/
#define TEGRA194_MAX_FRAME_IDLE_COUNT 0x1800
#define AMX_CH_REG(id, reg) ((reg) + ((id) * TEGRA210_AMX_AUDIOCIF_CH_STRIDE))
static const struct reg_default tegra210_amx_reg_defaults[] = {
{ TEGRA210_AMX_RX_INT_MASK, 0x0000000f},
{ TEGRA210_AMX_RX1_CIF_CTRL, 0x00007000},
{ TEGRA210_AMX_RX2_CIF_CTRL, 0x00007000},
{ TEGRA210_AMX_RX3_CIF_CTRL, 0x00007000},
{ TEGRA210_AMX_RX4_CIF_CTRL, 0x00007000},
{ TEGRA210_AMX_TX_INT_MASK, 0x00000001},
{ TEGRA210_AMX_TX_CIF_CTRL, 0x00007000},
{ TEGRA210_AMX_CG, 0x1},
{ TEGRA210_AMX_CFG_RAM_CTRL, 0x00004000},
};
static const struct reg_default tegra264_amx_reg_defaults[] = {
{ TEGRA210_AMX_RX_INT_MASK, 0x0000000f},
{ TEGRA210_AMX_RX1_CIF_CTRL, 0x00003800},
{ TEGRA210_AMX_RX2_CIF_CTRL, 0x00003800},
{ TEGRA210_AMX_RX3_CIF_CTRL, 0x00003800},
{ TEGRA210_AMX_RX4_CIF_CTRL, 0x00003800},
{ TEGRA210_AMX_TX_INT_MASK, 0x00000001},
{ TEGRA210_AMX_TX_CIF_CTRL, 0x00003800},
{ TEGRA210_AMX_CG, 0x1},
{ TEGRA264_AMX_CFG_RAM_CTRL, 0x00004000},
};
static void tegra210_amx_write_map_ram(struct tegra210_amx *amx)
{
const unsigned int bits_per_mask = BITS_PER_TYPE(*amx->byte_mask);
int i;
memset(amx->byte_mask, 0,
amx->soc_data->byte_mask_size * sizeof(*amx->byte_mask));
regmap_write(amx->regmap, TEGRA210_AMX_CFG_RAM_CTRL + amx->soc_data->reg_offset,
TEGRA210_AMX_CFG_RAM_CTRL_SEQ_ACCESS_EN |
TEGRA210_AMX_CFG_RAM_CTRL_ADDR_INIT_EN |
TEGRA210_AMX_CFG_RAM_CTRL_RW_WRITE);
for (i = 0; i < amx->soc_data->ram_depth; i++) {
u32 word = 0;
int b;
for (b = 0; b < TEGRA_AMX_SLOTS_PER_WORD; b++) {
unsigned int slot = i * TEGRA_AMX_SLOTS_PER_WORD + b;
u16 val = amx->map[slot];
if (val >= 256)
continue;
word |= (u32)val << (b * BITS_PER_BYTE);
amx->byte_mask[slot / bits_per_mask] |=
1U << (slot % bits_per_mask);
}
regmap_write(amx->regmap, TEGRA210_AMX_CFG_RAM_DATA + amx->soc_data->reg_offset,
word);
}
for (i = 0; i < amx->soc_data->byte_mask_size; i++)
regmap_write(amx->regmap,
TEGRA210_AMX_OUT_BYTE_EN0 + (i * TEGRA210_AMX_AUDIOCIF_CH_STRIDE),
amx->byte_mask[i]);
}
static int tegra210_amx_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct tegra210_amx *amx = snd_soc_dai_get_drvdata(dai);
unsigned int val;
int err;
/* Ensure if AMX is disabled */
err = regmap_read_poll_timeout(amx->regmap, TEGRA210_AMX_STATUS, val,
!(val & 0x1), 10, 10000);
if (err < 0) {
dev_err(dai->dev, "failed to stop AMX, err = %d\n", err);
return err;
}
/*
* Soft Reset: Below performs module soft reset which clears
* all FSM logic, flushes flow control of FIFO and resets the
* state register. It also brings module back to disabled
* state (without flushing the data in the pipe).
*/
regmap_update_bits(amx->regmap, TEGRA210_AMX_SOFT_RESET,
TEGRA210_AMX_SOFT_RESET_SOFT_RESET_MASK,
TEGRA210_AMX_SOFT_RESET_SOFT_EN);
err = regmap_read_poll_timeout(amx->regmap, TEGRA210_AMX_SOFT_RESET,
val, !(val & 0x1), 10, 10000);
if (err < 0) {
dev_err(dai->dev, "failed to reset AMX, err = %d\n", err);
return err;
}
return 0;
}
static int tegra210_amx_runtime_suspend(struct device *dev)
{
struct tegra210_amx *amx = dev_get_drvdata(dev);
regcache_cache_only(amx->regmap, true);
regcache_mark_dirty(amx->regmap);
return 0;
}
static int tegra210_amx_runtime_resume(struct device *dev)
{
struct tegra210_amx *amx = dev_get_drvdata(dev);
regcache_cache_only(amx->regmap, false);
regcache_sync(amx->regmap);
regmap_update_bits(amx->regmap,
TEGRA210_AMX_CTRL,
TEGRA210_AMX_CTRL_RX_DEP_MASK,
TEGRA210_AMX_WAIT_ON_ANY << TEGRA210_AMX_CTRL_RX_DEP_SHIFT);
tegra210_amx_write_map_ram(amx);
return 0;
}
static int tegra210_amx_set_audio_cif(struct snd_soc_dai *dai,
struct snd_pcm_hw_params *params,
unsigned int reg)
{
struct tegra210_amx *amx = snd_soc_dai_get_drvdata(dai);
int channels, audio_bits;
struct tegra_cif_conf cif_conf;
memset(&cif_conf, 0, sizeof(struct tegra_cif_conf));
channels = params_channels(params);
switch (params_format(params)) {
case SNDRV_PCM_FORMAT_S8:
audio_bits = TEGRA_ACIF_BITS_8;
break;
case SNDRV_PCM_FORMAT_S16_LE:
audio_bits = TEGRA_ACIF_BITS_16;
break;
case SNDRV_PCM_FORMAT_S24_LE:
case SNDRV_PCM_FORMAT_S32_LE:
audio_bits = TEGRA_ACIF_BITS_32;
break;
default:
dev_err(dai->dev, "unsupported format: %d\n",
params_format(params));
return -EINVAL;
}
cif_conf.audio_ch = channels;
cif_conf.client_ch = channels;
cif_conf.audio_bits = audio_bits;
cif_conf.client_bits = audio_bits;
if (amx->soc_data->max_ch == TEGRA264_AMX_MAX_CHANNEL)
tegra264_set_cif(amx->regmap, reg, &cif_conf);
else
tegra_set_cif(amx->regmap, reg, &cif_conf);
return 0;
}
static int tegra210_amx_in_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
struct tegra210_amx *amx = snd_soc_dai_get_drvdata(dai);
if (amx->soc_data->auto_disable) {
regmap_write(amx->regmap,
AMX_CH_REG(dai->id, TEGRA194_AMX_RX1_FRAME_PERIOD +
amx->soc_data->reg_offset),
TEGRA194_MAX_FRAME_IDLE_COUNT);
regmap_write(amx->regmap, TEGRA210_AMX_CYA + amx->soc_data->reg_offset, 1);
}
return tegra210_amx_set_audio_cif(dai, params,
AMX_CH_REG(dai->id, TEGRA210_AMX_RX1_CIF_CTRL));
}
static int tegra210_amx_out_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
return tegra210_amx_set_audio_cif(dai, params,
TEGRA210_AMX_TX_CIF_CTRL);
}
static int tegra210_amx_get_byte_map(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *cmpnt = snd_kcontrol_chip(kcontrol);
struct soc_mixer_control *mc =
(struct soc_mixer_control *)kcontrol->private_value;
struct tegra210_amx *amx = snd_soc_component_get_drvdata(cmpnt);
ucontrol->value.integer.value[0] = amx->map[mc->reg];
return 0;
}
static int tegra210_amx_put_byte_map(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct soc_mixer_control *mc =
(struct soc_mixer_control *)kcontrol->private_value;
struct snd_soc_component *cmpnt = snd_kcontrol_chip(kcontrol);
struct tegra210_amx *amx = snd_soc_component_get_drvdata(cmpnt);
unsigned int value = ucontrol->value.integer.value[0];
/*
* Match the previous behaviour: any value outside [0, 255] is
* treated as the "disabled" sentinel (256). Negative values from
* userspace fold in through the unsigned cast and are caught here.
*/
if (value > 255)
value = 256;
if (amx->map[mc->reg] == value)
return 0;
amx->map[mc->reg] = value;
return 1;
}
static const struct snd_soc_dai_ops tegra210_amx_out_dai_ops = {
.hw_params = tegra210_amx_out_hw_params,
.startup = tegra210_amx_startup,
};
static const struct snd_soc_dai_ops tegra210_amx_in_dai_ops = {
.hw_params = tegra210_amx_in_hw_params,
};
#define IN_DAI(id) \
{ \
.name = "AMX-RX-CIF" #id, \
.playback = { \
.stream_name = "RX" #id "-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_S24_LE | \
SNDRV_PCM_FMTBIT_S32_LE, \
}, \
.capture = { \
.stream_name = "RX" #id "-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_S24_LE | \
SNDRV_PCM_FMTBIT_S32_LE, \
}, \
.ops = &tegra210_amx_in_dai_ops, \
}
#define OUT_DAI \
{ \
.name = "AMX-TX-CIF", \
.playback = { \
.stream_name = "TX-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_S24_LE | \
SNDRV_PCM_FMTBIT_S32_LE, \
}, \
.capture = { \
.stream_name = "TX-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_S24_LE | \
SNDRV_PCM_FMTBIT_S32_LE, \
}, \
.ops = &tegra210_amx_out_dai_ops, \
}
static struct snd_soc_dai_driver tegra210_amx_dais[] = {
IN_DAI(1),
IN_DAI(2),
IN_DAI(3),
IN_DAI(4),
OUT_DAI,
};
static const struct snd_soc_dapm_widget tegra210_amx_widgets[] = {
SND_SOC_DAPM_AIF_IN("RX1", NULL, 0, TEGRA210_AMX_CTRL, 0, 0),
SND_SOC_DAPM_AIF_IN("RX2", NULL, 0, TEGRA210_AMX_CTRL, 1, 0),
SND_SOC_DAPM_AIF_IN("RX3", NULL, 0, TEGRA210_AMX_CTRL, 2, 0),
SND_SOC_DAPM_AIF_IN("RX4", NULL, 0, TEGRA210_AMX_CTRL, 3, 0),
SND_SOC_DAPM_AIF_OUT("TX", NULL, 0, TEGRA210_AMX_ENABLE,
TEGRA210_AMX_ENABLE_SHIFT, 0),
};
#define STREAM_ROUTES(id, sname) \
{ "RX" #id " XBAR-" sname, NULL, "RX" #id " XBAR-TX" }, \
{ "RX" #id "-CIF-" sname, NULL, "RX" #id " XBAR-" sname },\
{ "RX" #id, NULL, "RX" #id "-CIF-" sname }, \
{ "TX", NULL, "RX" #id }, \
{ "TX-CIF-" sname, NULL, "TX" }, \
{ "XBAR-" sname, NULL, "TX-CIF-" sname }, \
{ "XBAR-RX", NULL, "XBAR-" sname }
#define AMX_ROUTES(id) \
STREAM_ROUTES(id, "Playback"), \
STREAM_ROUTES(id, "Capture")
static const struct snd_soc_dapm_route tegra210_amx_routes[] = {
AMX_ROUTES(1),
AMX_ROUTES(2),
AMX_ROUTES(3),
AMX_ROUTES(4),
};
#define TEGRA210_AMX_BYTE_MAP_CTRL(reg) \
SOC_SINGLE_EXT("Byte Map " #reg, reg, 0, 256, 0, \
tegra210_amx_get_byte_map, \
tegra210_amx_put_byte_map)
static struct snd_kcontrol_new tegra210_amx_controls[] = {
TEGRA210_AMX_BYTE_MAP_CTRL(0),
TEGRA210_AMX_BYTE_MAP_CTRL(1),
TEGRA210_AMX_BYTE_MAP_CTRL(2),
TEGRA210_AMX_BYTE_MAP_CTRL(3),
TEGRA210_AMX_BYTE_MAP_CTRL(4),
TEGRA210_AMX_BYTE_MAP_CTRL(5),
TEGRA210_AMX_BYTE_MAP_CTRL(6),
TEGRA210_AMX_BYTE_MAP_CTRL(7),
TEGRA210_AMX_BYTE_MAP_CTRL(8),
TEGRA210_AMX_BYTE_MAP_CTRL(9),
TEGRA210_AMX_BYTE_MAP_CTRL(10),
TEGRA210_AMX_BYTE_MAP_CTRL(11),
TEGRA210_AMX_BYTE_MAP_CTRL(12),
TEGRA210_AMX_BYTE_MAP_CTRL(13),
TEGRA210_AMX_BYTE_MAP_CTRL(14),
TEGRA210_AMX_BYTE_MAP_CTRL(15),
TEGRA210_AMX_BYTE_MAP_CTRL(16),
TEGRA210_AMX_BYTE_MAP_CTRL(17),
TEGRA210_AMX_BYTE_MAP_CTRL(18),
TEGRA210_AMX_BYTE_MAP_CTRL(19),
TEGRA210_AMX_BYTE_MAP_CTRL(20),
TEGRA210_AMX_BYTE_MAP_CTRL(21),
TEGRA210_AMX_BYTE_MAP_CTRL(22),
TEGRA210_AMX_BYTE_MAP_CTRL(23),
TEGRA210_AMX_BYTE_MAP_CTRL(24),
TEGRA210_AMX_BYTE_MAP_CTRL(25),
TEGRA210_AMX_BYTE_MAP_CTRL(26),
TEGRA210_AMX_BYTE_MAP_CTRL(27),
TEGRA210_AMX_BYTE_MAP_CTRL(28),
TEGRA210_AMX_BYTE_MAP_CTRL(29),
TEGRA210_AMX_BYTE_MAP_CTRL(30),
TEGRA210_AMX_BYTE_MAP_CTRL(31),
TEGRA210_AMX_BYTE_MAP_CTRL(32),
TEGRA210_AMX_BYTE_MAP_CTRL(33),
TEGRA210_AMX_BYTE_MAP_CTRL(34),
TEGRA210_AMX_BYTE_MAP_CTRL(35),
TEGRA210_AMX_BYTE_MAP_CTRL(36),
TEGRA210_AMX_BYTE_MAP_CTRL(37),
TEGRA210_AMX_BYTE_MAP_CTRL(38),
TEGRA210_AMX_BYTE_MAP_CTRL(39),
TEGRA210_AMX_BYTE_MAP_CTRL(40),
TEGRA210_AMX_BYTE_MAP_CTRL(41),
TEGRA210_AMX_BYTE_MAP_CTRL(42),
TEGRA210_AMX_BYTE_MAP_CTRL(43),
TEGRA210_AMX_BYTE_MAP_CTRL(44),
TEGRA210_AMX_BYTE_MAP_CTRL(45),
TEGRA210_AMX_BYTE_MAP_CTRL(46),
TEGRA210_AMX_BYTE_MAP_CTRL(47),
TEGRA210_AMX_BYTE_MAP_CTRL(48),
TEGRA210_AMX_BYTE_MAP_CTRL(49),
TEGRA210_AMX_BYTE_MAP_CTRL(50),
TEGRA210_AMX_BYTE_MAP_CTRL(51),
TEGRA210_AMX_BYTE_MAP_CTRL(52),
TEGRA210_AMX_BYTE_MAP_CTRL(53),
TEGRA210_AMX_BYTE_MAP_CTRL(54),
TEGRA210_AMX_BYTE_MAP_CTRL(55),
TEGRA210_AMX_BYTE_MAP_CTRL(56),
TEGRA210_AMX_BYTE_MAP_CTRL(57),
TEGRA210_AMX_BYTE_MAP_CTRL(58),
TEGRA210_AMX_BYTE_MAP_CTRL(59),
TEGRA210_AMX_BYTE_MAP_CTRL(60),
TEGRA210_AMX_BYTE_MAP_CTRL(61),
TEGRA210_AMX_BYTE_MAP_CTRL(62),
TEGRA210_AMX_BYTE_MAP_CTRL(63),
};
static struct snd_kcontrol_new tegra264_amx_controls[] = {
TEGRA210_AMX_BYTE_MAP_CTRL(64),
TEGRA210_AMX_BYTE_MAP_CTRL(65),
TEGRA210_AMX_BYTE_MAP_CTRL(66),
TEGRA210_AMX_BYTE_MAP_CTRL(67),
TEGRA210_AMX_BYTE_MAP_CTRL(68),
TEGRA210_AMX_BYTE_MAP_CTRL(69),
TEGRA210_AMX_BYTE_MAP_CTRL(70),
TEGRA210_AMX_BYTE_MAP_CTRL(71),
TEGRA210_AMX_BYTE_MAP_CTRL(72),
TEGRA210_AMX_BYTE_MAP_CTRL(73),
TEGRA210_AMX_BYTE_MAP_CTRL(74),
TEGRA210_AMX_BYTE_MAP_CTRL(75),
TEGRA210_AMX_BYTE_MAP_CTRL(76),
TEGRA210_AMX_BYTE_MAP_CTRL(77),
TEGRA210_AMX_BYTE_MAP_CTRL(78),
TEGRA210_AMX_BYTE_MAP_CTRL(79),
TEGRA210_AMX_BYTE_MAP_CTRL(80),
TEGRA210_AMX_BYTE_MAP_CTRL(81),
TEGRA210_AMX_BYTE_MAP_CTRL(82),
TEGRA210_AMX_BYTE_MAP_CTRL(83),
TEGRA210_AMX_BYTE_MAP_CTRL(84),
TEGRA210_AMX_BYTE_MAP_CTRL(85),
TEGRA210_AMX_BYTE_MAP_CTRL(86),
TEGRA210_AMX_BYTE_MAP_CTRL(87),
TEGRA210_AMX_BYTE_MAP_CTRL(88),
TEGRA210_AMX_BYTE_MAP_CTRL(89),
TEGRA210_AMX_BYTE_MAP_CTRL(90),
TEGRA210_AMX_BYTE_MAP_CTRL(91),
TEGRA210_AMX_BYTE_MAP_CTRL(92),
TEGRA210_AMX_BYTE_MAP_CTRL(93),
TEGRA210_AMX_BYTE_MAP_CTRL(94),
TEGRA210_AMX_BYTE_MAP_CTRL(95),
TEGRA210_AMX_BYTE_MAP_CTRL(96),
TEGRA210_AMX_BYTE_MAP_CTRL(97),
TEGRA210_AMX_BYTE_MAP_CTRL(98),
TEGRA210_AMX_BYTE_MAP_CTRL(99),
TEGRA210_AMX_BYTE_MAP_CTRL(100),
TEGRA210_AMX_BYTE_MAP_CTRL(101),
TEGRA210_AMX_BYTE_MAP_CTRL(102),
TEGRA210_AMX_BYTE_MAP_CTRL(103),
TEGRA210_AMX_BYTE_MAP_CTRL(104),
TEGRA210_AMX_BYTE_MAP_CTRL(105),
TEGRA210_AMX_BYTE_MAP_CTRL(106),
TEGRA210_AMX_BYTE_MAP_CTRL(107),
TEGRA210_AMX_BYTE_MAP_CTRL(108),
TEGRA210_AMX_BYTE_MAP_CTRL(109),
TEGRA210_AMX_BYTE_MAP_CTRL(110),
TEGRA210_AMX_BYTE_MAP_CTRL(111),
TEGRA210_AMX_BYTE_MAP_CTRL(112),
TEGRA210_AMX_BYTE_MAP_CTRL(113),
TEGRA210_AMX_BYTE_MAP_CTRL(114),
TEGRA210_AMX_BYTE_MAP_CTRL(115),
TEGRA210_AMX_BYTE_MAP_CTRL(116),
TEGRA210_AMX_BYTE_MAP_CTRL(117),
TEGRA210_AMX_BYTE_MAP_CTRL(118),
TEGRA210_AMX_BYTE_MAP_CTRL(119),
TEGRA210_AMX_BYTE_MAP_CTRL(120),
TEGRA210_AMX_BYTE_MAP_CTRL(121),
TEGRA210_AMX_BYTE_MAP_CTRL(122),
TEGRA210_AMX_BYTE_MAP_CTRL(123),
TEGRA210_AMX_BYTE_MAP_CTRL(124),
TEGRA210_AMX_BYTE_MAP_CTRL(125),
TEGRA210_AMX_BYTE_MAP_CTRL(126),
TEGRA210_AMX_BYTE_MAP_CTRL(127),
};
static int tegra210_amx_component_probe(struct snd_soc_component *component)
{
struct tegra210_amx *amx = snd_soc_component_get_drvdata(component);
int err = 0;
if (amx->soc_data->num_controls) {
err = snd_soc_add_component_controls(component, amx->soc_data->controls,
amx->soc_data->num_controls);
if (err)
dev_err(component->dev, "can't add AMX controls, err: %d\n", err);
}
return err;
}
static const struct snd_soc_component_driver tegra210_amx_cmpnt = {
.probe = tegra210_amx_component_probe,
.dapm_widgets = tegra210_amx_widgets,
.num_dapm_widgets = ARRAY_SIZE(tegra210_amx_widgets),
.dapm_routes = tegra210_amx_routes,
.num_dapm_routes = ARRAY_SIZE(tegra210_amx_routes),
.controls = tegra210_amx_controls,
.num_controls = ARRAY_SIZE(tegra210_amx_controls),
};
static bool tegra210_amx_wr_reg(struct device *dev, unsigned int reg)
{
switch (reg) {
case TEGRA210_AMX_RX_INT_MASK ... TEGRA210_AMX_RX4_CIF_CTRL:
case TEGRA210_AMX_TX_INT_MASK ... TEGRA210_AMX_CG:
case TEGRA210_AMX_CTRL ... TEGRA210_AMX_CYA:
case TEGRA210_AMX_CFG_RAM_CTRL ... TEGRA210_AMX_CFG_RAM_DATA:
return true;
default:
return false;
}
}
static bool tegra194_amx_wr_reg(struct device *dev, unsigned int reg)
{
switch (reg) {
case TEGRA194_AMX_RX1_FRAME_PERIOD ... TEGRA194_AMX_RX4_FRAME_PERIOD:
return true;
default:
return tegra210_amx_wr_reg(dev, reg);
}
}
static bool tegra264_amx_wr_reg(struct device *dev,
unsigned int reg)
{
switch (reg) {
case TEGRA210_AMX_RX_INT_MASK ... TEGRA210_AMX_RX4_CIF_CTRL:
case TEGRA210_AMX_TX_INT_MASK ... TEGRA210_AMX_TX_CIF_CTRL:
case TEGRA210_AMX_ENABLE ... TEGRA210_AMX_CG:
case TEGRA210_AMX_CTRL ... TEGRA264_AMX_STREAMS_AUTO_DISABLE:
case TEGRA264_AMX_CFG_RAM_CTRL ... TEGRA264_AMX_CFG_RAM_DATA:
case TEGRA264_AMX_RX1_FRAME_PERIOD ... TEGRA264_AMX_RX4_FRAME_PERIOD:
return true;
default:
return false;
}
}
static bool tegra210_amx_rd_reg(struct device *dev, unsigned int reg)
{
switch (reg) {
case TEGRA210_AMX_RX_STATUS ... TEGRA210_AMX_CFG_RAM_DATA:
return true;
default:
return false;
}
}
static bool tegra194_amx_rd_reg(struct device *dev, unsigned int reg)
{
switch (reg) {
case TEGRA194_AMX_RX1_FRAME_PERIOD ... TEGRA194_AMX_RX4_FRAME_PERIOD:
return true;
default:
return tegra210_amx_rd_reg(dev, reg);
}
}
static bool tegra264_amx_rd_reg(struct device *dev,
unsigned int reg)
{
switch (reg) {
case TEGRA210_AMX_RX_STATUS ... TEGRA210_AMX_RX4_CIF_CTRL:
case TEGRA210_AMX_TX_STATUS ... TEGRA210_AMX_TX_CIF_CTRL:
case TEGRA210_AMX_ENABLE ... TEGRA210_AMX_INT_STATUS:
case TEGRA210_AMX_CTRL ... TEGRA264_AMX_CFG_RAM_DATA:
case TEGRA264_AMX_RX1_FRAME_PERIOD ... TEGRA264_AMX_RX4_FRAME_PERIOD:
return true;
default:
return false;
}
}
static bool tegra210_amx_volatile_reg(struct device *dev, unsigned int reg)
{
switch (reg) {
case TEGRA210_AMX_RX_STATUS:
case TEGRA210_AMX_RX_INT_STATUS:
case TEGRA210_AMX_RX_INT_SET:
case TEGRA210_AMX_TX_STATUS:
case TEGRA210_AMX_TX_INT_STATUS:
case TEGRA210_AMX_TX_INT_SET:
case TEGRA210_AMX_SOFT_RESET:
case TEGRA210_AMX_STATUS:
case TEGRA210_AMX_INT_STATUS:
case TEGRA210_AMX_CFG_RAM_CTRL:
case TEGRA210_AMX_CFG_RAM_DATA:
return true;
default:
break;
}
return false;
}
static bool tegra264_amx_volatile_reg(struct device *dev,
unsigned int reg)
{
switch (reg) {
case TEGRA210_AMX_RX_STATUS:
case TEGRA210_AMX_RX_INT_STATUS:
case TEGRA210_AMX_RX_INT_SET:
case TEGRA210_AMX_TX_STATUS:
case TEGRA210_AMX_TX_INT_STATUS:
case TEGRA210_AMX_TX_INT_SET:
case TEGRA210_AMX_SOFT_RESET:
case TEGRA210_AMX_STATUS:
case TEGRA210_AMX_INT_STATUS:
case TEGRA264_AMX_CFG_RAM_CTRL:
case TEGRA264_AMX_CFG_RAM_DATA:
return true;
default:
break;
}
return false;
}
static const struct regmap_config tegra210_amx_regmap_config = {
.reg_bits = 32,
.reg_stride = 4,
.val_bits = 32,
.max_register = TEGRA210_AMX_CFG_RAM_DATA,
.writeable_reg = tegra210_amx_wr_reg,
.readable_reg = tegra210_amx_rd_reg,
.volatile_reg = tegra210_amx_volatile_reg,
.reg_defaults = tegra210_amx_reg_defaults,
.num_reg_defaults = ARRAY_SIZE(tegra210_amx_reg_defaults),
.reg_default_cb = regmap_default_zero_cb,
.cache_type = REGCACHE_FLAT,
};
static const struct regmap_config tegra194_amx_regmap_config = {
.reg_bits = 32,
.reg_stride = 4,
.val_bits = 32,
.max_register = TEGRA194_AMX_RX4_LAST_FRAME_PERIOD,
.writeable_reg = tegra194_amx_wr_reg,
.readable_reg = tegra194_amx_rd_reg,
.volatile_reg = tegra210_amx_volatile_reg,
.reg_defaults = tegra210_amx_reg_defaults,
.num_reg_defaults = ARRAY_SIZE(tegra210_amx_reg_defaults),
.reg_default_cb = regmap_default_zero_cb,
.cache_type = REGCACHE_FLAT,
};
static const struct regmap_config tegra264_amx_regmap_config = {
.reg_bits = 32,
.reg_stride = 4,
.val_bits = 32,
.max_register = TEGRA264_AMX_RX4_LAST_FRAME_PERIOD,
.writeable_reg = tegra264_amx_wr_reg,
.readable_reg = tegra264_amx_rd_reg,
.volatile_reg = tegra264_amx_volatile_reg,
.reg_defaults = tegra264_amx_reg_defaults,
.num_reg_defaults = ARRAY_SIZE(tegra264_amx_reg_defaults),
.reg_default_cb = regmap_default_zero_cb,
.cache_type = REGCACHE_FLAT,
};
static const struct tegra210_amx_soc_data soc_data_tegra210 = {
.regmap_conf = &tegra210_amx_regmap_config,
.max_ch = TEGRA210_AMX_MAX_CHANNEL,
.ram_depth = TEGRA210_AMX_RAM_DEPTH,
.byte_mask_size = TEGRA210_AMX_BYTE_MASK_COUNT,
.reg_offset = TEGRA210_AMX_AUTO_DISABLE_OFFSET,
};
static const struct tegra210_amx_soc_data soc_data_tegra194 = {
.regmap_conf = &tegra194_amx_regmap_config,
.auto_disable = true,
.max_ch = TEGRA210_AMX_MAX_CHANNEL,
.ram_depth = TEGRA210_AMX_RAM_DEPTH,
.byte_mask_size = TEGRA210_AMX_BYTE_MASK_COUNT,
.reg_offset = TEGRA210_AMX_AUTO_DISABLE_OFFSET,
};
static const struct tegra210_amx_soc_data soc_data_tegra264 = {
.regmap_conf = &tegra264_amx_regmap_config,
.auto_disable = true,
.max_ch = TEGRA264_AMX_MAX_CHANNEL,
.ram_depth = TEGRA264_AMX_RAM_DEPTH,
.byte_mask_size = TEGRA264_AMX_BYTE_MASK_COUNT,
.reg_offset = TEGRA264_AMX_AUTO_DISABLE_OFFSET,
.controls = tegra264_amx_controls,
.num_controls = ARRAY_SIZE(tegra264_amx_controls),
};
static const struct of_device_id tegra210_amx_of_match[] = {
{ .compatible = "nvidia,tegra210-amx", .data = &soc_data_tegra210 },
{ .compatible = "nvidia,tegra194-amx", .data = &soc_data_tegra194 },
{ .compatible = "nvidia,tegra264-amx", .data = &soc_data_tegra264 },
{},
};
MODULE_DEVICE_TABLE(of, tegra210_amx_of_match);
static int tegra210_amx_platform_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct tegra210_amx *amx;
void __iomem *regs;
int err, i;
amx = devm_kzalloc(dev, sizeof(*amx), GFP_KERNEL);
if (!amx)
return -ENOMEM;
amx->soc_data = device_get_match_data(dev);
dev_set_drvdata(dev, amx);
regs = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(regs))
return PTR_ERR(regs);
amx->regmap = devm_regmap_init_mmio(dev, regs,
amx->soc_data->regmap_conf);
if (IS_ERR(amx->regmap))
return dev_err_probe(dev, PTR_ERR(amx->regmap),
"regmap init failed\n");
regcache_cache_only(amx->regmap, true);
amx->map = devm_kcalloc(dev,
amx->soc_data->ram_depth * TEGRA_AMX_SLOTS_PER_WORD,
sizeof(*amx->map), GFP_KERNEL);
if (!amx->map)
return -ENOMEM;
amx->byte_mask = devm_kcalloc(dev, amx->soc_data->byte_mask_size,
sizeof(*amx->byte_mask), GFP_KERNEL);
if (!amx->byte_mask)
return -ENOMEM;
/* Initialise all byte map slots as disabled (value 256). */
for (i = 0; i < amx->soc_data->ram_depth * TEGRA_AMX_SLOTS_PER_WORD; i++)
amx->map[i] = 256;
tegra210_amx_dais[TEGRA_AMX_OUT_DAI_ID].capture.channels_max =
amx->soc_data->max_ch;
err = devm_snd_soc_register_component(dev, &tegra210_amx_cmpnt,
tegra210_amx_dais,
ARRAY_SIZE(tegra210_amx_dais));
if (err)
return dev_err_probe(dev, err,
"can't register AMX component\n");
pm_runtime_enable(dev);
return 0;
}
static void tegra210_amx_platform_remove(struct platform_device *pdev)
{
pm_runtime_disable(&pdev->dev);
}
static const struct dev_pm_ops tegra210_amx_pm_ops = {
RUNTIME_PM_OPS(tegra210_amx_runtime_suspend,
tegra210_amx_runtime_resume, NULL)
SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume)
};
static struct platform_driver tegra210_amx_driver = {
.driver = {
.name = "tegra210-amx",
.of_match_table = tegra210_amx_of_match,
.pm = pm_ptr(&tegra210_amx_pm_ops),
},
.probe = tegra210_amx_platform_probe,
.remove = tegra210_amx_platform_remove,
};
module_platform_driver(tegra210_amx_driver);
MODULE_AUTHOR("Songhee Baek <sbaek@nvidia.com>");
MODULE_DESCRIPTION("Tegra210 AMX ASoC driver");
MODULE_LICENSE("GPL v2");
+116
View File
@@ -0,0 +1,116 @@
/* SPDX-License-Identifier: GPL-2.0-only
* SPDX-FileCopyrightText: Copyright (c) 2021-2025 NVIDIA CORPORATION. All rights reserved.
*
* tegra210_amx.h - Definitions for Tegra210 AMX driver
*
*/
#ifndef __TEGRA210_AMX_H__
#define __TEGRA210_AMX_H__
#include <linux/types.h>
/* Register offsets from TEGRA210_AMX*_BASE */
#define TEGRA210_AMX_RX_STATUS 0x0c
#define TEGRA210_AMX_RX_INT_STATUS 0x10
#define TEGRA210_AMX_RX_INT_MASK 0x14
#define TEGRA210_AMX_RX_INT_SET 0x18
#define TEGRA210_AMX_RX_INT_CLEAR 0x1c
#define TEGRA210_AMX_RX1_CIF_CTRL 0x20
#define TEGRA210_AMX_RX2_CIF_CTRL 0x24
#define TEGRA210_AMX_RX3_CIF_CTRL 0x28
#define TEGRA210_AMX_RX4_CIF_CTRL 0x2c
#define TEGRA210_AMX_TX_STATUS 0x4c
#define TEGRA210_AMX_TX_INT_STATUS 0x50
#define TEGRA210_AMX_TX_INT_MASK 0x54
#define TEGRA210_AMX_TX_INT_SET 0x58
#define TEGRA210_AMX_TX_INT_CLEAR 0x5c
#define TEGRA210_AMX_TX_CIF_CTRL 0x60
#define TEGRA210_AMX_ENABLE 0x80
#define TEGRA210_AMX_SOFT_RESET 0x84
#define TEGRA210_AMX_CG 0x88
#define TEGRA210_AMX_STATUS 0x8c
#define TEGRA210_AMX_INT_STATUS 0x90
#define TEGRA210_AMX_CTRL 0xa4
#define TEGRA210_AMX_OUT_BYTE_EN0 0xa8
#define TEGRA210_AMX_CYA 0xb0
#define TEGRA210_AMX_CFG_RAM_CTRL 0xb8
#define TEGRA210_AMX_CFG_RAM_DATA 0xbc
#define TEGRA194_AMX_RX1_FRAME_PERIOD 0xc0
#define TEGRA194_AMX_RX4_FRAME_PERIOD 0xcc
#define TEGRA194_AMX_RX4_LAST_FRAME_PERIOD 0xdc
#define TEGRA264_AMX_STREAMS_AUTO_DISABLE 0xb8
#define TEGRA264_AMX_CFG_RAM_CTRL 0xc0
#define TEGRA264_AMX_CFG_RAM_DATA 0xc4
#define TEGRA264_AMX_RX1_FRAME_PERIOD 0xc8
#define TEGRA264_AMX_RX4_FRAME_PERIOD 0xd4
#define TEGRA264_AMX_RX4_LAST_FRAME_PERIOD 0xe4
/* Fields in TEGRA210_AMX_ENABLE */
#define TEGRA210_AMX_ENABLE_SHIFT 0
/* Fields in TEGRA210_AMX_CTRL */
#define TEGRA210_AMX_CTRL_MSTR_RX_NUM_SHIFT 14
#define TEGRA210_AMX_CTRL_MSTR_RX_NUM_MASK (3 << TEGRA210_AMX_CTRL_MSTR_RX_NUM_SHIFT)
#define TEGRA210_AMX_CTRL_RX_DEP_SHIFT 12
#define TEGRA210_AMX_CTRL_RX_DEP_MASK (3 << TEGRA210_AMX_CTRL_RX_DEP_SHIFT)
/* Fields in TEGRA210_AMX_CFG_RAM_CTRL */
#define TEGRA210_AMX_CFG_RAM_CTRL_RW_SHIFT 14
#define TEGRA210_AMX_CFG_RAM_CTRL_RW_WRITE (1 << TEGRA210_AMX_CFG_RAM_CTRL_RW_SHIFT)
#define TEGRA210_AMX_CFG_RAM_CTRL_ADDR_INIT_EN_SHIFT 13
#define TEGRA210_AMX_CFG_RAM_CTRL_ADDR_INIT_EN (1 << TEGRA210_AMX_CFG_RAM_CTRL_ADDR_INIT_EN_SHIFT)
#define TEGRA210_AMX_CFG_RAM_CTRL_SEQ_ACCESS_EN_SHIFT 12
#define TEGRA210_AMX_CFG_RAM_CTRL_SEQ_ACCESS_EN (1 << TEGRA210_AMX_CFG_RAM_CTRL_SEQ_ACCESS_EN_SHIFT)
#define TEGRA210_AMX_CFG_CTRL_RAM_ADDR_SHIFT 0
/* Fields in TEGRA210_AMX_SOFT_RESET */
#define TEGRA210_AMX_SOFT_RESET_SOFT_EN 1
#define TEGRA210_AMX_SOFT_RESET_SOFT_RESET_MASK TEGRA210_AMX_SOFT_RESET_SOFT_EN
#define TEGRA210_AMX_AUDIOCIF_CH_STRIDE 4
#define TEGRA_AMX_SLOTS_PER_WORD 4
#define TEGRA210_AMX_RAM_DEPTH 16
#define TEGRA210_AMX_MAP_STREAM_NUM_SHIFT 6
#define TEGRA210_AMX_MAP_WORD_NUM_SHIFT 2
#define TEGRA210_AMX_MAP_BYTE_NUM_SHIFT 0
#define TEGRA210_AMX_BYTE_MASK_COUNT 2
#define TEGRA210_AMX_MAX_CHANNEL 16
#define TEGRA210_AMX_AUTO_DISABLE_OFFSET 0
#define TEGRA264_AMX_RAM_DEPTH 32
#define TEGRA264_AMX_BYTE_MASK_COUNT 4
#define TEGRA264_AMX_MAX_CHANNEL 32
#define TEGRA264_AMX_AUTO_DISABLE_OFFSET 8
#define TEGRA_AMX_OUT_DAI_ID 4
enum {
TEGRA210_AMX_WAIT_ON_ALL,
TEGRA210_AMX_WAIT_ON_ANY,
};
struct tegra210_amx_soc_data {
const struct regmap_config *regmap_conf;
bool auto_disable;
const struct snd_kcontrol_new *controls;
unsigned int num_controls;
unsigned int max_ch;
unsigned int ram_depth;
unsigned int byte_mask_size;
unsigned int reg_offset;
};
struct tegra210_amx {
const struct tegra210_amx_soc_data *soc_data;
unsigned int *byte_mask;
u16 *map;
struct regmap *regmap;
};
#endif
+567
View File
@@ -0,0 +1,567 @@
// SPDX-License-Identifier: GPL-2.0-only
// SPDX-FileCopyrightText: Copyright (c) 2020-2024 NVIDIA CORPORATION & AFFILIATES.
// All rights reserved.
//
// tegra210_dmic.c - Tegra210 DMIC driver
#include <linux/clk.h>
#include <linux/device.h>
#include <linux/math64.h>
#include <linux/module.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_S24_LE:
case SNDRV_PCM_FORMAT_S32_LE:
cif_conf.audio_bits = TEGRA_ACIF_BITS_32;
break;
default:
dev_err(dai->dev, "unsupported format!\n");
return -EOPNOTSUPP;
}
cif_conf.client_bits = TEGRA_ACIF_BITS_24;
cif_conf.mono_conv = dmic->mono_to_stereo;
cif_conf.stereo_conv = dmic->stereo_to_mono;
tegra_set_cif(dmic->regmap, TEGRA210_DMIC_TX_CIF_CTRL, &cif_conf);
return 0;
}
static int tegra210_dmic_get_boost_gain(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
struct tegra210_dmic *dmic = snd_soc_component_get_drvdata(comp);
ucontrol->value.integer.value[0] = dmic->boost_gain;
return 0;
}
static int tegra210_dmic_put_boost_gain(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
struct tegra210_dmic *dmic = snd_soc_component_get_drvdata(comp);
int value = ucontrol->value.integer.value[0];
if (value == dmic->boost_gain)
return 0;
dmic->boost_gain = value;
return 1;
}
static int tegra210_dmic_get_ch_select(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
struct tegra210_dmic *dmic = snd_soc_component_get_drvdata(comp);
ucontrol->value.enumerated.item[0] = dmic->ch_select;
return 0;
}
static int tegra210_dmic_put_ch_select(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
struct tegra210_dmic *dmic = snd_soc_component_get_drvdata(comp);
unsigned int value = ucontrol->value.enumerated.item[0];
if (value == dmic->ch_select)
return 0;
dmic->ch_select = value;
return 1;
}
static int tegra210_dmic_get_mono_to_stereo(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
struct tegra210_dmic *dmic = snd_soc_component_get_drvdata(comp);
ucontrol->value.enumerated.item[0] = dmic->mono_to_stereo;
return 0;
}
static int tegra210_dmic_put_mono_to_stereo(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
struct tegra210_dmic *dmic = snd_soc_component_get_drvdata(comp);
unsigned int value = ucontrol->value.enumerated.item[0];
if (value == dmic->mono_to_stereo)
return 0;
dmic->mono_to_stereo = value;
return 1;
}
static int tegra210_dmic_get_stereo_to_mono(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
struct tegra210_dmic *dmic = snd_soc_component_get_drvdata(comp);
ucontrol->value.enumerated.item[0] = dmic->stereo_to_mono;
return 0;
}
static int tegra210_dmic_put_stereo_to_mono(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
struct tegra210_dmic *dmic = snd_soc_component_get_drvdata(comp);
unsigned int value = ucontrol->value.enumerated.item[0];
if (value == dmic->stereo_to_mono)
return 0;
dmic->stereo_to_mono = value;
return 1;
}
static int tegra210_dmic_get_osr_val(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
struct tegra210_dmic *dmic = snd_soc_component_get_drvdata(comp);
ucontrol->value.enumerated.item[0] = dmic->osr_val;
return 0;
}
static int tegra210_dmic_put_osr_val(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
struct tegra210_dmic *dmic = snd_soc_component_get_drvdata(comp);
unsigned int value = ucontrol->value.enumerated.item[0];
if (value == dmic->osr_val)
return 0;
dmic->osr_val = value;
return 1;
}
static int tegra210_dmic_get_pol_sel(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
struct tegra210_dmic *dmic = snd_soc_component_get_drvdata(comp);
ucontrol->value.enumerated.item[0] = dmic->lrsel;
return 0;
}
static int tegra210_dmic_put_pol_sel(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
struct tegra210_dmic *dmic = snd_soc_component_get_drvdata(comp);
unsigned int value = ucontrol->value.enumerated.item[0];
if (value == dmic->lrsel)
return 0;
dmic->lrsel = value;
return 1;
}
static const struct snd_soc_dai_ops tegra210_dmic_dai_ops = {
.hw_params = tegra210_dmic_hw_params,
};
static struct snd_soc_dai_driver tegra210_dmic_dais[] = {
{
.name = "DMIC-CIF",
.capture = {
.stream_name = "CIF-Capture",
.channels_min = 1,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_8000_48000,
.formats = SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_S24_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_S24_LE |
SNDRV_PCM_FMTBIT_S32_LE,
},
.ops = &tegra210_dmic_dai_ops,
.symmetric_rate = 1,
},
};
static const struct snd_soc_dapm_widget tegra210_dmic_widgets[] = {
SND_SOC_DAPM_AIF_OUT("TX", NULL, 0, TEGRA210_DMIC_ENABLE, 0, 0),
SND_SOC_DAPM_MIC("MIC", NULL),
};
static const struct snd_soc_dapm_route tegra210_dmic_routes[] = {
{ "XBAR-RX", NULL, "XBAR-Capture" },
{ "XBAR-Capture", NULL, "CIF-Capture" },
{ "CIF-Capture", NULL, "TX" },
{ "TX", NULL, "DAP-Capture" },
{ "DAP-Capture", NULL, "MIC" },
};
static const char * const tegra210_dmic_ch_select[] = {
"Left", "Right", "Stereo",
};
static const struct soc_enum tegra210_dmic_ch_enum =
SOC_ENUM_SINGLE(0, 0, ARRAY_SIZE(tegra210_dmic_ch_select),
tegra210_dmic_ch_select);
static const char * const tegra210_dmic_mono_conv_text[] = {
"Zero", "Copy",
};
static const char * const tegra210_dmic_stereo_conv_text[] = {
"CH0", "CH1", "AVG",
};
static const struct soc_enum tegra210_dmic_mono_conv_enum =
SOC_ENUM_SINGLE(0, 0, ARRAY_SIZE(tegra210_dmic_mono_conv_text),
tegra210_dmic_mono_conv_text);
static const struct soc_enum tegra210_dmic_stereo_conv_enum =
SOC_ENUM_SINGLE(0, 0, ARRAY_SIZE(tegra210_dmic_stereo_conv_text),
tegra210_dmic_stereo_conv_text);
static const char * const tegra210_dmic_osr_text[] = {
"OSR_64", "OSR_128", "OSR_256",
};
static const struct soc_enum tegra210_dmic_osr_enum =
SOC_ENUM_SINGLE(0, 0, ARRAY_SIZE(tegra210_dmic_osr_text),
tegra210_dmic_osr_text);
static const char * const tegra210_dmic_lrsel_text[] = {
"Left", "Right",
};
static const struct soc_enum tegra210_dmic_lrsel_enum =
SOC_ENUM_SINGLE(0, 0, ARRAY_SIZE(tegra210_dmic_lrsel_text),
tegra210_dmic_lrsel_text);
static const struct snd_kcontrol_new tegra210_dmic_controls[] = {
SOC_SINGLE_EXT("Boost Gain Volume", 0, 0, MAX_BOOST_GAIN, 0,
tegra210_dmic_get_boost_gain,
tegra210_dmic_put_boost_gain),
SOC_ENUM_EXT("Channel Select", tegra210_dmic_ch_enum,
tegra210_dmic_get_ch_select, tegra210_dmic_put_ch_select),
SOC_ENUM_EXT("Mono To Stereo",
tegra210_dmic_mono_conv_enum,
tegra210_dmic_get_mono_to_stereo,
tegra210_dmic_put_mono_to_stereo),
SOC_ENUM_EXT("Stereo To Mono",
tegra210_dmic_stereo_conv_enum,
tegra210_dmic_get_stereo_to_mono,
tegra210_dmic_put_stereo_to_mono),
SOC_ENUM_EXT("OSR Value", tegra210_dmic_osr_enum,
tegra210_dmic_get_osr_val, tegra210_dmic_put_osr_val),
SOC_ENUM_EXT("LR Polarity Select", tegra210_dmic_lrsel_enum,
tegra210_dmic_get_pol_sel, tegra210_dmic_put_pol_sel),
};
static const struct snd_soc_component_driver tegra210_dmic_compnt = {
.dapm_widgets = tegra210_dmic_widgets,
.num_dapm_widgets = ARRAY_SIZE(tegra210_dmic_widgets),
.dapm_routes = tegra210_dmic_routes,
.num_dapm_routes = ARRAY_SIZE(tegra210_dmic_routes),
.controls = tegra210_dmic_controls,
.num_controls = ARRAY_SIZE(tegra210_dmic_controls),
};
static bool tegra210_dmic_wr_reg(struct device *dev, unsigned int reg)
{
switch (reg) {
case TEGRA210_DMIC_TX_INT_MASK ... TEGRA210_DMIC_TX_CIF_CTRL:
case TEGRA210_DMIC_ENABLE ... TEGRA210_DMIC_CG:
case TEGRA210_DMIC_CTRL:
case TEGRA210_DMIC_DBG_CTRL:
case TEGRA210_DMIC_DCR_BIQUAD_0_COEF_4 ... TEGRA210_DMIC_LP_BIQUAD_1_COEF_4:
return true;
default:
return false;
}
}
static bool tegra210_dmic_rd_reg(struct device *dev, unsigned int reg)
{
if (tegra210_dmic_wr_reg(dev, reg))
return true;
switch (reg) {
case TEGRA210_DMIC_TX_STATUS:
case TEGRA210_DMIC_TX_INT_STATUS:
case TEGRA210_DMIC_STATUS:
case TEGRA210_DMIC_INT_STATUS:
return true;
default:
return false;
}
}
static bool tegra210_dmic_volatile_reg(struct device *dev, unsigned int reg)
{
switch (reg) {
case TEGRA210_DMIC_TX_STATUS:
case TEGRA210_DMIC_TX_INT_STATUS:
case TEGRA210_DMIC_TX_INT_SET:
case TEGRA210_DMIC_SOFT_RESET:
case TEGRA210_DMIC_STATUS:
case TEGRA210_DMIC_INT_STATUS:
return true;
default:
return false;
}
}
static const struct regmap_config tegra210_dmic_regmap_config = {
.reg_bits = 32,
.reg_stride = 4,
.val_bits = 32,
.max_register = TEGRA210_DMIC_LP_BIQUAD_1_COEF_4,
.writeable_reg = tegra210_dmic_wr_reg,
.readable_reg = tegra210_dmic_rd_reg,
.volatile_reg = tegra210_dmic_volatile_reg,
.reg_defaults = tegra210_dmic_reg_defaults,
.num_reg_defaults = ARRAY_SIZE(tegra210_dmic_reg_defaults),
.reg_default_cb = regmap_default_zero_cb,
.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))
return dev_err_probe(dev, PTR_ERR(dmic->clk_dmic),
"can't retrieve DMIC clock\n");
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))
return dev_err_probe(dev, PTR_ERR(dmic->regmap),
"regmap init failed\n");
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)
return dev_err_probe(dev, err,
"can't register DMIC component\n");
pm_runtime_enable(dev);
return 0;
}
static void tegra210_dmic_remove(struct platform_device *pdev)
{
pm_runtime_disable(&pdev->dev);
}
static const struct dev_pm_ops tegra210_dmic_pm_ops = {
RUNTIME_PM_OPS(tegra210_dmic_runtime_suspend,
tegra210_dmic_runtime_resume, NULL)
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 = pm_ptr(&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");
@@ -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
File diff suppressed because it is too large Load Diff
+178
View File
@@ -0,0 +1,178 @@
/* SPDX-License-Identifier: GPL-2.0-only
* SPDX-FileCopyrightText: Copyright (c) 2020-2025 NVIDIA CORPORATION & AFFILIATES.
* All rights reserved.
*
* tegra210_i2s.h - Definitions for Tegra210 I2S driver
*
*/
#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
/* T264 specific registers */
#define TEGRA264_I2S_RX_FIFO_WR_ACCESS_MODE 0x30
#define TEGRA264_I2S_RX_CYA 0x3c
#define TEGRA264_I2S_RX_CIF_FIFO_STATUS 0x40
#define TEGRA264_I2S_TX_ENABLE 0x80
#define TEGRA264_I2S_TX_SOFT_RESET 0x84
#define TEGRA264_I2S_TX_STATUS 0x8c
#define TEGRA264_I2S_TX_INT_STATUS 0x90
#define TEGRA264_I2S_TX_INT_MASK 0x94
#define TEGRA264_I2S_TX_CIF_CTRL 0xa0
#define TEGRA264_I2S_TX_FIFO_RD_ACCESS_MODE 0xb0
#define TEGRA264_I2S_TX_FIFO_RD_DATA 0xb4
#define TEGRA264_I2S_TX_FIFO_THRESHOLD 0xb8
#define TEGRA264_I2S_TX_CYA 0xbc
#define TEGRA264_I2S_TX_CIF_FIFO_STATUS 0xc0
#define TEGRA264_I2S_ENABLE 0x100
#define TEGRA264_I2S_CG 0x108
#define TEGRA264_I2S_STATUS 0x10c
#define TEGRA264_I2S_INT_STATUS 0x110
#define TEGRA264_I2S_INT_SET 0x114
#define TEGRA264_I2S_INT_MASK 0x11c
#define TEGRA264_I2S_CTRL 0x12c
#define TEGRA264_I2S_TIMING 0x130
#define TEGRA264_I2S_CYA 0x13c
#define TEGRA264_I2S_PIO_MODE_ENABLE 0x140
#define TEGRA264_I2S_PAD_MACRO_STATUS 0x144
/* Bit fields, shifts and masks */
#define I2S_DATA_SHIFT 8
#define I2S_CTRL_DATA_OFFSET_MASK (0x7ff << I2S_DATA_SHIFT)
#define TEGRA264_I2S_FSYNC_WIDTH_SHIFT 23
#define TEGRA264_I2S_CTRL_FSYNC_WIDTH_MASK (0x1ff << TEGRA264_I2S_FSYNC_WIDTH_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_24 5
#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
#define TEGRA210_I2S_TX_OFFSET 0
#define TEGRA210_I2S_CTRL_OFFSET 0
#define TEGRA210_I2S_MAX_CHANNEL 16
#define TEGRA264_DEFAULT_I2S_SLOT_MASK 0xffffffff
#define TEGRA264_I2S_TX_OFFSET 0x40
#define TEGRA264_I2S_CTRL_OFFSET 0x8c
#define TEGRA264_I2S_MAX_CHANNEL 32
enum tegra210_i2s_path {
I2S_RX_PATH,
I2S_TX_PATH,
I2S_PATHS,
};
struct tegra_i2s_soc_data {
const struct regmap_config *regmap_conf;
const struct snd_soc_component_driver *i2s_cmpnt;
unsigned int max_ch;
unsigned int tx_offset;
unsigned int i2s_ctrl_offset;
unsigned int fsync_width_mask;
unsigned int fsync_width_shift;
unsigned int slot_mask;
};
struct tegra210_i2s {
const struct tegra_i2s_soc_data *soc_data;
struct clk *clk_i2s;
struct clk *clk_sync_input;
struct regmap *regmap;
int client_sample_format;
unsigned int client_channels;
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
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,215 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* tegra210_mbdrc.h - Definitions for Tegra210 MBDRC driver
*
* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved.
*
*/
#ifndef __TEGRA210_MBDRC_H__
#define __TEGRA210_MBDRC_H__
#include <linux/platform_device.h>
#include <sound/soc.h>
/* Register offsets from TEGRA210_MBDRC*_BASE */
#define TEGRA210_MBDRC_SOFT_RESET 0x4
#define TEGRA210_MBDRC_CG 0x8
#define TEGRA210_MBDRC_STATUS 0xc
#define TEGRA210_MBDRC_CFG 0x28
#define TEGRA210_MBDRC_CHANNEL_MASK 0x2c
#define TEGRA210_MBDRC_MASTER_VOL 0x30
#define TEGRA210_MBDRC_FAST_FACTOR 0x34
#define TEGRA210_MBDRC_FILTER_COUNT 3
#define TEGRA210_MBDRC_FILTER_PARAM_STRIDE 0x4
#define TEGRA210_MBDRC_IIR_CFG 0x38
#define TEGRA210_MBDRC_IN_ATTACK 0x44
#define TEGRA210_MBDRC_IN_RELEASE 0x50
#define TEGRA210_MBDRC_FAST_ATTACK 0x5c
#define TEGRA210_MBDRC_IN_THRESHOLD 0x68
#define TEGRA210_MBDRC_OUT_THRESHOLD 0x74
#define TEGRA210_MBDRC_RATIO_1ST 0x80
#define TEGRA210_MBDRC_RATIO_2ND 0x8c
#define TEGRA210_MBDRC_RATIO_3RD 0x98
#define TEGRA210_MBDRC_RATIO_4TH 0xa4
#define TEGRA210_MBDRC_RATIO_5TH 0xb0
#define TEGRA210_MBDRC_MAKEUP_GAIN 0xbc
#define TEGRA210_MBDRC_INIT_GAIN 0xc8
#define TEGRA210_MBDRC_GAIN_ATTACK 0xd4
#define TEGRA210_MBDRC_GAIN_RELEASE 0xe0
#define TEGRA210_MBDRC_FAST_RELEASE 0xec
#define TEGRA210_MBDRC_CFG_RAM_CTRL 0xf8
#define TEGRA210_MBDRC_CFG_RAM_DATA 0x104
#define TEGRA210_MBDRC_MAX_REG (TEGRA210_MBDRC_CFG_RAM_DATA + \
(TEGRA210_MBDRC_FILTER_PARAM_STRIDE * \
(TEGRA210_MBDRC_FILTER_COUNT - 1)))
/* Fields for TEGRA210_MBDRC_CFG */
#define TEGRA210_MBDRC_CFG_RMS_OFFSET_SHIFT 16
#define TEGRA210_MBDRC_CFG_RMS_OFFSET_MASK (0x1ff << TEGRA210_MBDRC_CFG_RMS_OFFSET_SHIFT)
#define TEGRA210_MBDRC_CFG_PEAK_RMS_SHIFT 14
#define TEGRA210_MBDRC_CFG_PEAK_RMS_MASK (0x1 << TEGRA210_MBDRC_CFG_PEAK_RMS_SHIFT)
#define TEGRA210_MBDRC_CFG_PEAK (1 << TEGRA210_MBDRC_CFG_PEAK_RMS_SHIFT)
#define TEGRA210_MBDRC_CFG_FILTER_STRUCTURE_SHIFT 13
#define TEGRA210_MBDRC_CFG_FILTER_STRUCTURE_MASK (0x1 << TEGRA210_MBDRC_CFG_FILTER_STRUCTURE_SHIFT)
#define TEGRA210_MBDRC_CFG_FILTER_STRUCTURE_FLEX (1 << TEGRA210_MBDRC_CFG_FILTER_STRUCTURE_SHIFT)
#define TEGRA210_MBDRC_CFG_SHIFT_CTRL_SHIFT 8
#define TEGRA210_MBDRC_CFG_SHIFT_CTRL_MASK (0x1f << TEGRA210_MBDRC_CFG_SHIFT_CTRL_SHIFT)
#define TEGRA210_MBDRC_CFG_FRAME_SIZE_SHIFT 4
#define TEGRA210_MBDRC_CFG_FRAME_SIZE_MASK (0xf << TEGRA210_MBDRC_CFG_FRAME_SIZE_SHIFT)
#define TEGRA210_MBDRC_CFG_MBDRC_MODE_SHIFT 0
#define TEGRA210_MBDRC_CFG_MBDRC_MODE_MASK (0x3 << TEGRA210_MBDRC_CFG_MBDRC_MODE_SHIFT)
#define TEGRA210_MBDRC_CFG_MBDRC_MODE_BYPASS (0 << TEGRA210_MBDRC_CFG_MBDRC_MODE_SHIFT)
/* Fields for TEGRA210_MBDRC_CHANNEL_MASK */
#define TEGRA210_MBDRC_CHANNEL_MASK_SHIFT 0
#define TEGRA210_MBDRC_CHANNEL_MASK_MASK (0xff << TEGRA210_MBDRC_CHANNEL_MASK_SHIFT)
/* Fields for TEGRA210_MBDRC_MASTER_VOL */
#define TEGRA210_MBDRC_MASTER_VOL_SHIFT 23
#define TEGRA210_MBDRC_MASTER_VOL_MIN -256
#define TEGRA210_MBDRC_MASTER_VOL_MAX 256
/* Fields for TEGRA210_MBDRC_FAST_FACTOR */
#define TEGRA210_MBDRC_FAST_FACTOR_RELEASE_SHIFT 16
#define TEGRA210_MBDRC_FAST_FACTOR_RELEASE_MASK (0xffff << TEGRA210_MBDRC_FAST_FACTOR_RELEASE_SHIFT)
#define TEGRA210_MBDRC_FAST_FACTOR_ATTACK_SHIFT 0
#define TEGRA210_MBDRC_FAST_FACTOR_ATTACK_MASK (0xffff << TEGRA210_MBDRC_FAST_FACTOR_ATTACK_SHIFT)
/* Fields for TEGRA210_MBDRC_IIR_CFG */
#define TEGRA210_MBDRC_IIR_CFG_NUM_STAGES_SHIFT 0
#define TEGRA210_MBDRC_IIR_CFG_NUM_STAGES_MASK (0xf << TEGRA210_MBDRC_IIR_CFG_NUM_STAGES_SHIFT)
/* Fields for TEGRA210_MBDRC_IN_ATTACK */
#define TEGRA210_MBDRC_IN_ATTACK_TC_SHIFT 0
#define TEGRA210_MBDRC_IN_ATTACK_TC_MASK (0xffffffff << TEGRA210_MBDRC_IN_ATTACK_TC_SHIFT)
/* Fields for TEGRA210_MBDRC_IN_RELEASE */
#define TEGRA210_MBDRC_IN_RELEASE_TC_SHIFT 0
#define TEGRA210_MBDRC_IN_RELEASE_TC_MASK (0xffffffff << TEGRA210_MBDRC_IN_RELEASE_TC_SHIFT)
/* Fields for TEGRA210_MBDRC_FAST_ATTACK */
#define TEGRA210_MBDRC_FAST_ATTACK_TC_SHIFT 0
#define TEGRA210_MBDRC_FAST_ATTACK_TC_MASK (0xffffffff << TEGRA210_MBDRC_FAST_ATTACK_TC_SHIFT)
/* Fields for TEGRA210_MBDRC_IN_THRESHOLD / TEGRA210_MBDRC_OUT_THRESHOLD */
#define TEGRA210_MBDRC_THRESH_4TH_SHIFT 24
#define TEGRA210_MBDRC_THRESH_4TH_MASK (0xff << TEGRA210_MBDRC_THRESH_4TH_SHIFT)
#define TEGRA210_MBDRC_THRESH_3RD_SHIFT 16
#define TEGRA210_MBDRC_THRESH_3RD_MASK (0xff << TEGRA210_MBDRC_THRESH_3RD_SHIFT)
#define TEGRA210_MBDRC_THRESH_2ND_SHIFT 8
#define TEGRA210_MBDRC_THRESH_2ND_MASK (0xff << TEGRA210_MBDRC_THRESH_2ND_SHIFT)
#define TEGRA210_MBDRC_THRESH_1ST_SHIFT 0
#define TEGRA210_MBDRC_THRESH_1ST_MASK (0xff << TEGRA210_MBDRC_THRESH_1ST_SHIFT)
/* Fields for TEGRA210_MBDRC_RATIO_1ST */
#define TEGRA210_MBDRC_RATIO_1ST_SHIFT 0
#define TEGRA210_MBDRC_RATIO_1ST_MASK (0xffff << TEGRA210_MBDRC_RATIO_1ST_SHIFT)
/* Fields for TEGRA210_MBDRC_RATIO_2ND */
#define TEGRA210_MBDRC_RATIO_2ND_SHIFT 0
#define TEGRA210_MBDRC_RATIO_2ND_MASK (0xffff << TEGRA210_MBDRC_RATIO_2ND_SHIFT)
/* Fields for TEGRA210_MBDRC_RATIO_3RD */
#define TEGRA210_MBDRC_RATIO_3RD_SHIFT 0
#define TEGRA210_MBDRC_RATIO_3RD_MASK (0xffff << TEGRA210_MBDRC_RATIO_3RD_SHIFT)
/* Fields for TEGRA210_MBDRC_RATIO_4TH */
#define TEGRA210_MBDRC_RATIO_4TH_SHIFT 0
#define TEGRA210_MBDRC_RATIO_4TH_MASK (0xffff << TEGRA210_MBDRC_RATIO_4TH_SHIFT)
/* Fields for TEGRA210_MBDRC_RATIO_5TH */
#define TEGRA210_MBDRC_RATIO_5TH_SHIFT 0
#define TEGRA210_MBDRC_RATIO_5TH_MASK (0xffff << TEGRA210_MBDRC_RATIO_5TH_SHIFT)
/* Fields for TEGRA210_MBDRC_MAKEUP_GAIN */
#define TEGRA210_MBDRC_MAKEUP_GAIN_SHIFT 0
#define TEGRA210_MBDRC_MAKEUP_GAIN_MASK (0x3f << TEGRA210_MBDRC_MAKEUP_GAIN_SHIFT)
/* Fields for TEGRA210_MBDRC_INIT_GAIN */
#define TEGRA210_MBDRC_INIT_GAIN_SHIFT 0
#define TEGRA210_MBDRC_INIT_GAIN_MASK (0xffffffff << TEGRA210_MBDRC_INIT_GAIN_SHIFT)
/* Fields for TEGRA210_MBDRC_GAIN_ATTACK */
#define TEGRA210_MBDRC_GAIN_ATTACK_SHIFT 0
#define TEGRA210_MBDRC_GAIN_ATTACK_MASK (0xffffffff << TEGRA210_MBDRC_GAIN_ATTACK_SHIFT)
/* Fields for TEGRA210_MBDRC_GAIN_RELEASE */
#define TEGRA210_MBDRC_GAIN_RELEASE_SHIFT 0
#define TEGRA210_MBDRC_GAIN_RELEASE_MASK (0xffffffff << TEGRA210_MBDRC_GAIN_RELEASE_SHIFT)
/* Fields for TEGRA210_MBDRC_FAST_RELEASE */
#define TEGRA210_MBDRC_FAST_RELEASE_SHIFT 0
#define TEGRA210_MBDRC_FAST_RELEASE_MASK (0xffffffff << TEGRA210_MBDRC_FAST_RELEASE_SHIFT)
#define TEGRA210_MBDRC_RAM_CTRL_RW_READ 0
#define TEGRA210_MBDRC_RAM_CTRL_RW_WRITE (1 << 14)
#define TEGRA210_MBDRC_RAM_CTRL_ADDR_INIT_EN (1 << 13)
#define TEGRA210_MBDRC_RAM_CTRL_SEQ_ACCESS_EN (1 << 12)
#define TEGRA210_MBDRC_RAM_CTRL_RAM_ADDR_MASK 0x1ff
/*
* Order and size of each structure element for following structures should not
* be altered size order of elements and their size are based on PEQ co-eff ram
* and shift ram layout.
*/
#define TEGRA210_MBDRC_THRESHOLD_NUM 4
#define TEGRA210_MBDRC_RATIO_NUM (TEGRA210_MBDRC_THRESHOLD_NUM + 1)
#define TEGRA210_MBDRC_MAX_BIQUAD_STAGES 8
/* Order of these enums are same as the order of band specific hw registers */
enum {
MBDRC_LOW_BAND,
MBDRC_MID_BAND,
MBDRC_HIGH_BAND,
MBDRC_NUM_BAND,
};
struct tegra210_mbdrc_band_params {
u32 band;
u32 iir_stages;
u32 in_attack_tc;
u32 in_release_tc;
u32 fast_attack_tc;
u32 in_threshold[TEGRA210_MBDRC_THRESHOLD_NUM];
u32 out_threshold[TEGRA210_MBDRC_THRESHOLD_NUM];
u32 ratio[TEGRA210_MBDRC_RATIO_NUM];
u32 makeup_gain;
u32 gain_init;
u32 gain_attack_tc;
u32 gain_release_tc;
u32 fast_release_tc;
/* For biquad_params[][5] order of coeff is b0, b1, a0, a1, a2 */
u32 biquad_params[TEGRA210_MBDRC_MAX_BIQUAD_STAGES * 5];
};
struct tegra210_mbdrc_config {
unsigned int mode;
unsigned int rms_off;
unsigned int peak_rms_mode;
unsigned int filter_structure;
unsigned int shift_ctrl;
unsigned int frame_size;
unsigned int channel_mask;
unsigned int fa_factor; /* Fast attack factor */
unsigned int fr_factor; /* Fast release factor */
struct tegra210_mbdrc_band_params band_params[MBDRC_NUM_BAND];
};
int tegra210_mbdrc_regmap_init(struct platform_device *pdev);
int tegra210_mbdrc_component_init(struct snd_soc_component *cmpnt);
int tegra210_mbdrc_hw_params(struct snd_soc_component *cmpnt);
#endif
@@ -0,0 +1,959 @@
// SPDX-License-Identifier: GPL-2.0-only
// SPDX-FileCopyrightText: Copyright (c) 2021-2024 NVIDIA CORPORATION & AFFILIATES.
// All rights reserved.
//
// tegra210_mixer.c - Tegra210 MIXER driver
#include <linux/clk.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/math64.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include "tegra210_mixer.h"
#include "tegra_cif.h"
#define MIXER_REG(reg, id) ((reg) + ((id) * TEGRA210_MIXER_REG_STRIDE))
#define MIXER_REG_BASE(reg) ((reg) % TEGRA210_MIXER_REG_STRIDE)
#define MIXER_GAIN_CFG_RAM_ADDR(id) \
(TEGRA210_MIXER_GAIN_CFG_RAM_ADDR_0 + \
((id) * TEGRA210_MIXER_GAIN_CFG_RAM_ADDR_STRIDE))
#define MIXER_RX_REG_DEFAULTS(id) \
{ MIXER_REG(TEGRA210_MIXER_RX1_CIF_CTRL, id), 0x00007700}, \
{ MIXER_REG(TEGRA210_MIXER_RX1_CTRL, id), 0x00010823}, \
{ MIXER_REG(TEGRA210_MIXER_RX1_PEAK_CTRL, id), 0x000012c0}
#define MIXER_TX_REG_DEFAULTS(id) \
{ MIXER_REG(TEGRA210_MIXER_TX1_INT_MASK, (id)), 0x00000001}, \
{ MIXER_REG(TEGRA210_MIXER_TX1_CIF_CTRL, (id)), 0x00007700}
#define REG_DURATION_PARAM(reg, i) ((reg) + NUM_GAIN_POLY_COEFFS + 1 + (i))
static const struct reg_default tegra210_mixer_reg_defaults[] = {
/* Inputs */
MIXER_RX_REG_DEFAULTS(0),
MIXER_RX_REG_DEFAULTS(1),
MIXER_RX_REG_DEFAULTS(2),
MIXER_RX_REG_DEFAULTS(3),
MIXER_RX_REG_DEFAULTS(4),
MIXER_RX_REG_DEFAULTS(5),
MIXER_RX_REG_DEFAULTS(6),
MIXER_RX_REG_DEFAULTS(7),
MIXER_RX_REG_DEFAULTS(8),
MIXER_RX_REG_DEFAULTS(9),
/* Outputs */
MIXER_TX_REG_DEFAULTS(0),
MIXER_TX_REG_DEFAULTS(1),
MIXER_TX_REG_DEFAULTS(2),
MIXER_TX_REG_DEFAULTS(3),
MIXER_TX_REG_DEFAULTS(4),
{ TEGRA210_MIXER_CG, 0x00000001},
{ TEGRA210_MIXER_GAIN_CFG_RAM_CTRL, 0x00004000},
{ TEGRA210_MIXER_PEAKM_RAM_CTRL, 0x00004000},
{ TEGRA210_MIXER_ENABLE, 0x1 },
};
/* Default gain parameters */
static const struct tegra210_mixer_gain_params gain_params = {
/* Polynomial coefficients */
{ 0, 0, 0, 0, 0, 0, 0, 0x1000000, 0 },
/* Gain value */
0x10000,
/* Duration Parameters */
{ 0, 0, 0x400, 0x8000000 },
};
static int tegra210_mixer_runtime_suspend(struct device *dev)
{
struct tegra210_mixer *mixer = dev_get_drvdata(dev);
regcache_cache_only(mixer->regmap, true);
regcache_mark_dirty(mixer->regmap);
return 0;
}
static int tegra210_mixer_runtime_resume(struct device *dev)
{
struct tegra210_mixer *mixer = dev_get_drvdata(dev);
regcache_cache_only(mixer->regmap, false);
regcache_sync(mixer->regmap);
return 0;
}
static int tegra210_mixer_write_ram(struct tegra210_mixer *mixer,
unsigned int addr,
unsigned int coef)
{
unsigned int reg, val;
int err;
/* Check if busy */
err = regmap_read_poll_timeout(mixer->regmap,
TEGRA210_MIXER_GAIN_CFG_RAM_CTRL,
val, !(val & 0x80000000), 10, 10000);
if (err < 0)
return err;
reg = (addr << TEGRA210_MIXER_GAIN_CFG_RAM_ADDR_SHIFT) &
TEGRA210_MIXER_GAIN_CFG_RAM_ADDR_MASK;
reg |= TEGRA210_MIXER_GAIN_CFG_RAM_ADDR_INIT_EN;
reg |= TEGRA210_MIXER_GAIN_CFG_RAM_RW_WRITE;
reg |= TEGRA210_MIXER_GAIN_CFG_RAM_SEQ_ACCESS_EN;
regmap_write(mixer->regmap,
TEGRA210_MIXER_GAIN_CFG_RAM_CTRL,
reg);
regmap_write(mixer->regmap,
TEGRA210_MIXER_GAIN_CFG_RAM_DATA,
coef);
return 0;
}
static int tegra210_mixer_configure_gain(struct snd_soc_component *cmpnt,
unsigned int id, bool instant_gain)
{
struct tegra210_mixer *mixer = snd_soc_component_get_drvdata(cmpnt);
unsigned int reg = MIXER_GAIN_CFG_RAM_ADDR(id);
int err, i;
pm_runtime_get_sync(cmpnt->dev);
/* Write default gain poly coefficients */
for (i = 0; i < NUM_GAIN_POLY_COEFFS; i++) {
err = tegra210_mixer_write_ram(mixer, reg + i,
gain_params.poly_coeff[i]);
if (err < 0)
goto rpm_put;
}
/* Write stored gain value */
err = tegra210_mixer_write_ram(mixer, reg + NUM_GAIN_POLY_COEFFS,
mixer->gain_value[id]);
if (err < 0)
goto rpm_put;
/* Write duration parameters */
for (i = 0; i < NUM_DURATION_PARMS; i++) {
u32 val;
if (instant_gain) {
val = 1;
} else {
if (i == DURATION_N3_ID)
val = mixer->duration[id];
else if (i == DURATION_INV_N3_ID)
val = div_u64(BIT_ULL(31 + TEGRA210_MIXER_PRESCALAR),
mixer->duration[id]);
else
val = gain_params.duration[i];
}
err = tegra210_mixer_write_ram(mixer,
REG_DURATION_PARAM(reg, i),
val);
if (err < 0)
goto rpm_put;
}
/* Trigger to apply gain configurations */
err = tegra210_mixer_write_ram(mixer, reg + REG_CFG_DONE_TRIGGER,
VAL_CFG_DONE_TRIGGER);
rpm_put:
pm_runtime_put(cmpnt->dev);
return err;
}
static int tegra210_mixer_fade_duration_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = 1;
uinfo->value.integer.min = TEGRA210_MIXER_FADE_DURATION_MIN;
uinfo->value.integer.max = TEGRA210_MIXER_FADE_DURATION_MAX;
return 0;
}
static int tegra210_mixer_get_fade_duration(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct soc_mixer_control *mc =
(struct soc_mixer_control *)kcontrol->private_value;
struct snd_soc_component *cmpnt = snd_kcontrol_chip(kcontrol);
struct tegra210_mixer *mixer = snd_soc_component_get_drvdata(cmpnt);
ucontrol->value.integer.value[0] = mixer->duration[mc->reg];
return 0;
}
static int tegra210_mixer_put_fade_duration(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct soc_mixer_control *mc =
(struct soc_mixer_control *)kcontrol->private_value;
struct snd_soc_component *cmpnt = snd_kcontrol_chip(kcontrol);
struct tegra210_mixer *mixer = snd_soc_component_get_drvdata(cmpnt);
unsigned int id = mc->reg;
long duration = ucontrol->value.integer.value[0];
if (duration < (long)TEGRA210_MIXER_FADE_DURATION_MIN ||
duration > TEGRA210_MIXER_FADE_DURATION_MAX)
return -EINVAL;
if (mixer->duration[id] == duration)
return 0;
mixer->duration[id] = duration;
mixer->fade_pending[id] = true;
return 1;
}
static int tegra210_mixer_get_fade_gain(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct soc_mixer_control *mc =
(struct soc_mixer_control *)kcontrol->private_value;
struct snd_soc_component *cmpnt = snd_kcontrol_chip(kcontrol);
struct tegra210_mixer *mixer = snd_soc_component_get_drvdata(cmpnt);
ucontrol->value.integer.value[0] = mixer->fade_gain[mc->reg];
return 0;
}
static int tegra210_mixer_put_fade_gain(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct soc_mixer_control *mc =
(struct soc_mixer_control *)kcontrol->private_value;
struct snd_soc_component *cmpnt = snd_kcontrol_chip(kcontrol);
struct tegra210_mixer *mixer = snd_soc_component_get_drvdata(cmpnt);
unsigned int id = mc->reg;
if (ucontrol->value.integer.value[0] < 0 ||
ucontrol->value.integer.value[0] > TEGRA210_MIXER_GAIN_MAX)
return -EINVAL;
if (mixer->fade_gain[id] == ucontrol->value.integer.value[0])
return 0;
mixer->fade_gain[id] = ucontrol->value.integer.value[0];
mixer->fade_pending[id] = true;
return 1;
}
static int tegra210_mixer_get_fade_switch(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
ucontrol->value.integer.value[0] = 0;
return 0;
}
static int tegra210_mixer_put_fade_switch(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *cmpnt = snd_kcontrol_chip(kcontrol);
struct tegra210_mixer *mixer = snd_soc_component_get_drvdata(cmpnt);
int id, err, changed = 0;
err = pm_runtime_resume_and_get(cmpnt->dev);
if (err < 0)
return err;
/* Switch off: disable sample count for all active fades */
if (!ucontrol->value.integer.value[0]) {
for (id = 0; id < TEGRA210_MIXER_RX_MAX; id++) {
if (!mixer->in_fade[id])
continue;
regmap_update_bits(mixer->regmap,
MIXER_REG(TEGRA210_MIXER_RX1_CTRL,
id),
TEGRA210_MIXER_SAMPLE_COUNT_ENABLE,
0);
mixer->in_fade[id] = false;
changed = 1;
}
pm_runtime_put(cmpnt->dev);
return changed;
}
/* Stop active fades on pending streams before reconfiguring */
for (id = 0; id < TEGRA210_MIXER_RX_MAX; id++) {
if (!mixer->fade_pending[id])
continue;
if (mixer->in_fade[id]) {
regmap_update_bits(mixer->regmap,
MIXER_REG(TEGRA210_MIXER_RX1_CTRL,
id),
TEGRA210_MIXER_SAMPLE_COUNT_ENABLE,
0);
mixer->in_fade[id] = false;
}
mixer->gain_value[id] = mixer->fade_gain[id];
err = tegra210_mixer_configure_gain(cmpnt, id, false);
if (err) {
dev_err(cmpnt->dev,
"Failed to configure fade for RX%d\n", id + 1);
pm_runtime_put(cmpnt->dev);
return err;
}
changed = 1;
}
if (!changed) {
pm_runtime_put(cmpnt->dev);
return 0;
}
/* Enable sample count for all pending streams */
for (id = 0; id < TEGRA210_MIXER_RX_MAX; id++) {
if (!mixer->fade_pending[id])
continue;
err = regmap_update_bits(mixer->regmap,
MIXER_REG(TEGRA210_MIXER_RX1_CTRL, id),
TEGRA210_MIXER_SAMPLE_COUNT_ENABLE,
TEGRA210_MIXER_SAMPLE_COUNT_ENABLE);
if (err) {
dev_err(cmpnt->dev,
"Failed to enable sample count for RX%d\n",
id + 1);
pm_runtime_put(cmpnt->dev);
return err;
}
mixer->in_fade[id] = true;
mixer->fade_pending[id] = false;
}
pm_runtime_put(cmpnt->dev);
return 1;
}
static int tegra210_mixer_get_fade_status(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *cmpnt = snd_kcontrol_chip(kcontrol);
struct tegra210_mixer *mixer = snd_soc_component_get_drvdata(cmpnt);
u32 count;
int id, err;
err = pm_runtime_resume_and_get(cmpnt->dev);
if (err < 0)
return err;
for (id = 0; id < TEGRA210_MIXER_RX_MAX; id++) {
if (!mixer->in_fade[id]) {
ucontrol->value.integer.value[id] = TEGRA210_MIXER_FADE_IDLE;
continue;
}
regmap_read(mixer->regmap,
MIXER_REG(TEGRA210_MIXER_RX1_SAMPLE_COUNT, id),
&count);
if (count >= mixer->duration[id])
ucontrol->value.integer.value[id] = TEGRA210_MIXER_FADE_IDLE;
else
ucontrol->value.integer.value[id] = TEGRA210_MIXER_FADE_ACTIVE;
}
pm_runtime_put(cmpnt->dev);
return 0;
}
static int tegra210_mixer_get_gain(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct soc_mixer_control *mc =
(struct soc_mixer_control *)kcontrol->private_value;
struct snd_soc_component *cmpnt = snd_kcontrol_chip(kcontrol);
struct tegra210_mixer *mixer = snd_soc_component_get_drvdata(cmpnt);
unsigned int reg = mc->reg;
unsigned int i;
i = (reg - TEGRA210_MIXER_GAIN_CFG_RAM_ADDR_0) /
TEGRA210_MIXER_GAIN_CFG_RAM_ADDR_STRIDE;
ucontrol->value.integer.value[0] = mixer->gain_value[i];
return 0;
}
static int tegra210_mixer_apply_gain(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol,
bool instant_gain)
{
struct soc_mixer_control *mc =
(struct soc_mixer_control *)kcontrol->private_value;
struct snd_soc_component *cmpnt = snd_kcontrol_chip(kcontrol);
struct tegra210_mixer *mixer = snd_soc_component_get_drvdata(cmpnt);
unsigned int reg = mc->reg, id;
int err;
/* Save gain value for specific MIXER input */
id = (reg - TEGRA210_MIXER_GAIN_CFG_RAM_ADDR_0) /
TEGRA210_MIXER_GAIN_CFG_RAM_ADDR_STRIDE;
if (mixer->gain_value[id] == ucontrol->value.integer.value[0])
return 0;
mixer->gain_value[id] = ucontrol->value.integer.value[0];
err = tegra210_mixer_configure_gain(cmpnt, id, instant_gain);
if (err) {
dev_err(cmpnt->dev, "Failed to apply gain\n");
return err;
}
return 1;
}
static int tegra210_mixer_put_gain(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
return tegra210_mixer_apply_gain(kcontrol, ucontrol, false);
}
static int tegra210_mixer_put_instant_gain(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
return tegra210_mixer_apply_gain(kcontrol, ucontrol, true);
}
static int tegra210_mixer_set_audio_cif(struct tegra210_mixer *mixer,
struct snd_pcm_hw_params *params,
unsigned int reg,
unsigned int id)
{
unsigned int channels, audio_bits;
struct tegra_cif_conf cif_conf;
memset(&cif_conf, 0, sizeof(struct tegra_cif_conf));
channels = params_channels(params);
switch (params_format(params)) {
case SNDRV_PCM_FORMAT_S16_LE:
audio_bits = TEGRA_ACIF_BITS_16;
break;
case SNDRV_PCM_FORMAT_S24_LE:
case SNDRV_PCM_FORMAT_S32_LE:
audio_bits = TEGRA_ACIF_BITS_32;
break;
default:
return -EINVAL;
}
cif_conf.audio_ch = channels;
cif_conf.client_ch = channels;
cif_conf.audio_bits = audio_bits;
cif_conf.client_bits = audio_bits;
tegra_set_cif(mixer->regmap,
reg + (id * TEGRA210_MIXER_REG_STRIDE),
&cif_conf);
return 0;
}
static int tegra210_mixer_in_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
struct tegra210_mixer *mixer = snd_soc_dai_get_drvdata(dai);
int err;
err = tegra210_mixer_set_audio_cif(mixer, params,
TEGRA210_MIXER_RX1_CIF_CTRL,
dai->id);
if (err < 0)
return err;
return tegra210_mixer_configure_gain(dai->component, dai->id, false);
}
static int tegra210_mixer_out_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
struct tegra210_mixer *mixer = snd_soc_dai_get_drvdata(dai);
return tegra210_mixer_set_audio_cif(mixer, params,
TEGRA210_MIXER_TX1_CIF_CTRL,
dai->id - TEGRA210_MIXER_RX_MAX);
}
static const struct snd_soc_dai_ops tegra210_mixer_out_dai_ops = {
.hw_params = tegra210_mixer_out_hw_params,
};
static const struct snd_soc_dai_ops tegra210_mixer_in_dai_ops = {
.hw_params = tegra210_mixer_in_hw_params,
};
#define IN_DAI(id) \
{ \
.name = "MIXER-RX-CIF"#id, \
.playback = { \
.stream_name = "RX" #id "-CIF-Playback",\
.channels_min = 1, \
.channels_max = 8, \
.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 = "RX" #id "-CIF-Capture", \
.channels_min = 1, \
.channels_max = 8, \
.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, \
}, \
.ops = &tegra210_mixer_in_dai_ops, \
}
#define OUT_DAI(id) \
{ \
.name = "MIXER-TX-CIF" #id, \
.playback = { \
.stream_name = "TX" #id "-CIF-Playback",\
.channels_min = 1, \
.channels_max = 8, \
.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 = "TX" #id "-CIF-Capture", \
.channels_min = 1, \
.channels_max = 8, \
.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, \
}, \
.ops = &tegra210_mixer_out_dai_ops, \
}
static struct snd_soc_dai_driver tegra210_mixer_dais[] = {
/* Mixer Input */
IN_DAI(1),
IN_DAI(2),
IN_DAI(3),
IN_DAI(4),
IN_DAI(5),
IN_DAI(6),
IN_DAI(7),
IN_DAI(8),
IN_DAI(9),
IN_DAI(10),
/* Mixer Output */
OUT_DAI(1),
OUT_DAI(2),
OUT_DAI(3),
OUT_DAI(4),
OUT_DAI(5),
};
#define ADDER_CTRL_DECL(name, reg) \
static const struct snd_kcontrol_new name[] = { \
SOC_DAPM_SINGLE("RX1", reg, 0, 1, 0), \
SOC_DAPM_SINGLE("RX2", reg, 1, 1, 0), \
SOC_DAPM_SINGLE("RX3", reg, 2, 1, 0), \
SOC_DAPM_SINGLE("RX4", reg, 3, 1, 0), \
SOC_DAPM_SINGLE("RX5", reg, 4, 1, 0), \
SOC_DAPM_SINGLE("RX6", reg, 5, 1, 0), \
SOC_DAPM_SINGLE("RX7", reg, 6, 1, 0), \
SOC_DAPM_SINGLE("RX8", reg, 7, 1, 0), \
SOC_DAPM_SINGLE("RX9", reg, 8, 1, 0), \
SOC_DAPM_SINGLE("RX10", reg, 9, 1, 0), \
}
ADDER_CTRL_DECL(adder1, TEGRA210_MIXER_TX1_ADDER_CONFIG);
ADDER_CTRL_DECL(adder2, TEGRA210_MIXER_TX2_ADDER_CONFIG);
ADDER_CTRL_DECL(adder3, TEGRA210_MIXER_TX3_ADDER_CONFIG);
ADDER_CTRL_DECL(adder4, TEGRA210_MIXER_TX4_ADDER_CONFIG);
ADDER_CTRL_DECL(adder5, TEGRA210_MIXER_TX5_ADDER_CONFIG);
static int tegra210_mixer_fade_status_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = TEGRA210_MIXER_RX_MAX;
uinfo->value.integer.min = TEGRA210_MIXER_FADE_IDLE;
uinfo->value.integer.max = TEGRA210_MIXER_FADE_ACTIVE;
return 0;
}
#define FADE_CTRL(id) \
{ \
.iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
.name = "RX" #id " Fade Duration", \
.info = tegra210_mixer_fade_duration_info, \
.get = tegra210_mixer_get_fade_duration, \
.put = tegra210_mixer_put_fade_duration, \
.private_value = SOC_SINGLE_VALUE((id) - 1, 0, \
TEGRA210_MIXER_FADE_DURATION_MIN, \
TEGRA210_MIXER_FADE_DURATION_MAX, \
0, 0), \
}, \
SOC_SINGLE_EXT("RX" #id " Fade Gain", (id) - 1, 0, \
TEGRA210_MIXER_GAIN_MAX, 0, \
tegra210_mixer_get_fade_gain, \
tegra210_mixer_put_fade_gain),
#define GAIN_CTRL(id) \
SOC_SINGLE_EXT("RX" #id " Gain Volume", \
MIXER_GAIN_CFG_RAM_ADDR((id) - 1), 0, \
TEGRA210_MIXER_GAIN_MAX, 0, \
tegra210_mixer_get_gain, \
tegra210_mixer_put_gain), \
SOC_SINGLE_EXT("RX" #id " Instant Gain Volume", \
MIXER_GAIN_CFG_RAM_ADDR((id) - 1), 0, \
TEGRA210_MIXER_GAIN_MAX, 0, \
tegra210_mixer_get_gain, \
tegra210_mixer_put_instant_gain),
/* Volume controls for all MIXER inputs */
static const struct snd_kcontrol_new tegra210_mixer_gain_ctls[] = {
GAIN_CTRL(1)
GAIN_CTRL(2)
GAIN_CTRL(3)
GAIN_CTRL(4)
GAIN_CTRL(5)
GAIN_CTRL(6)
GAIN_CTRL(7)
GAIN_CTRL(8)
GAIN_CTRL(9)
GAIN_CTRL(10)
FADE_CTRL(1)
FADE_CTRL(2)
FADE_CTRL(3)
FADE_CTRL(4)
FADE_CTRL(5)
FADE_CTRL(6)
FADE_CTRL(7)
FADE_CTRL(8)
FADE_CTRL(9)
FADE_CTRL(10)
SOC_SINGLE_EXT("Fade Switch", SND_SOC_NOPM, 0, 1, 0,
tegra210_mixer_get_fade_switch,
tegra210_mixer_put_fade_switch),
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Fade Status",
.info = tegra210_mixer_fade_status_info,
.access = SNDRV_CTL_ELEM_ACCESS_READ |
SNDRV_CTL_ELEM_ACCESS_VOLATILE,
.get = tegra210_mixer_get_fade_status,
},
};
static const struct snd_soc_dapm_widget tegra210_mixer_widgets[] = {
SND_SOC_DAPM_AIF_IN("RX1", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_IN("RX2", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_IN("RX3", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_IN("RX4", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_IN("RX5", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_IN("RX6", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_IN("RX7", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_IN("RX8", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_IN("RX9", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_IN("RX10", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_OUT("TX1", NULL, 0, TEGRA210_MIXER_TX1_ENABLE, 0, 0),
SND_SOC_DAPM_AIF_OUT("TX2", NULL, 0, TEGRA210_MIXER_TX2_ENABLE, 0, 0),
SND_SOC_DAPM_AIF_OUT("TX3", NULL, 0, TEGRA210_MIXER_TX3_ENABLE, 0, 0),
SND_SOC_DAPM_AIF_OUT("TX4", NULL, 0, TEGRA210_MIXER_TX4_ENABLE, 0, 0),
SND_SOC_DAPM_AIF_OUT("TX5", NULL, 0, TEGRA210_MIXER_TX5_ENABLE, 0, 0),
SND_SOC_DAPM_MIXER("Adder1", SND_SOC_NOPM, 1, 0, adder1,
ARRAY_SIZE(adder1)),
SND_SOC_DAPM_MIXER("Adder2", SND_SOC_NOPM, 1, 0, adder2,
ARRAY_SIZE(adder2)),
SND_SOC_DAPM_MIXER("Adder3", SND_SOC_NOPM, 1, 0, adder3,
ARRAY_SIZE(adder3)),
SND_SOC_DAPM_MIXER("Adder4", SND_SOC_NOPM, 1, 0, adder4,
ARRAY_SIZE(adder4)),
SND_SOC_DAPM_MIXER("Adder5", SND_SOC_NOPM, 1, 0, adder5,
ARRAY_SIZE(adder5)),
};
#define RX_ROUTES(id, sname) \
{ "RX" #id " XBAR-" sname, NULL, "RX" #id " XBAR-TX" }, \
{ "RX" #id "-CIF-" sname, NULL, "RX" #id " XBAR-" sname }, \
{ "RX" #id, NULL, "RX" #id "-CIF-" sname }
#define MIXER_RX_ROUTES(id) \
RX_ROUTES(id, "Playback"), \
RX_ROUTES(id, "Capture")
#define ADDER_ROUTES(id, sname) \
{ "Adder" #id, "RX1", "RX1" }, \
{ "Adder" #id, "RX2", "RX2" }, \
{ "Adder" #id, "RX3", "RX3" }, \
{ "Adder" #id, "RX4", "RX4" }, \
{ "Adder" #id, "RX5", "RX5" }, \
{ "Adder" #id, "RX6", "RX6" }, \
{ "Adder" #id, "RX7", "RX7" }, \
{ "Adder" #id, "RX8", "RX8" }, \
{ "Adder" #id, "RX9", "RX9" }, \
{ "Adder" #id, "RX10", "RX10" }, \
{ "TX" #id, NULL, "Adder" #id }, \
{ "TX" #id "-CIF-" sname, NULL, "TX" #id }, \
{ "TX" #id " XBAR-" sname, NULL, "TX" #id "-CIF-" sname }, \
{ "TX" #id " XBAR-RX", NULL, "TX" #id " XBAR-" sname } \
#define TX_ROUTES(id, sname) \
ADDER_ROUTES(1, sname), \
ADDER_ROUTES(2, sname), \
ADDER_ROUTES(3, sname), \
ADDER_ROUTES(4, sname), \
ADDER_ROUTES(5, sname)
#define MIXER_TX_ROUTES(id) \
TX_ROUTES(id, "Playback"), \
TX_ROUTES(id, "Capture")
static const struct snd_soc_dapm_route tegra210_mixer_routes[] = {
/* Input */
MIXER_RX_ROUTES(1),
MIXER_RX_ROUTES(2),
MIXER_RX_ROUTES(3),
MIXER_RX_ROUTES(4),
MIXER_RX_ROUTES(5),
MIXER_RX_ROUTES(6),
MIXER_RX_ROUTES(7),
MIXER_RX_ROUTES(8),
MIXER_RX_ROUTES(9),
MIXER_RX_ROUTES(10),
/* Output */
MIXER_TX_ROUTES(1),
MIXER_TX_ROUTES(2),
MIXER_TX_ROUTES(3),
MIXER_TX_ROUTES(4),
MIXER_TX_ROUTES(5),
};
static const struct snd_soc_component_driver tegra210_mixer_cmpnt = {
.dapm_widgets = tegra210_mixer_widgets,
.num_dapm_widgets = ARRAY_SIZE(tegra210_mixer_widgets),
.dapm_routes = tegra210_mixer_routes,
.num_dapm_routes = ARRAY_SIZE(tegra210_mixer_routes),
.controls = tegra210_mixer_gain_ctls,
.num_controls = ARRAY_SIZE(tegra210_mixer_gain_ctls),
};
static bool tegra210_mixer_wr_reg(struct device *dev,
unsigned int reg)
{
if (reg < TEGRA210_MIXER_RX_LIMIT)
reg = MIXER_REG_BASE(reg);
else if (reg < TEGRA210_MIXER_TX_LIMIT)
reg = MIXER_REG_BASE(reg) + TEGRA210_MIXER_TX1_ENABLE;
switch (reg) {
case TEGRA210_MIXER_RX1_SOFT_RESET:
case TEGRA210_MIXER_RX1_CIF_CTRL ... TEGRA210_MIXER_RX1_PEAK_CTRL:
case TEGRA210_MIXER_TX1_ENABLE:
case TEGRA210_MIXER_TX1_SOFT_RESET:
case TEGRA210_MIXER_TX1_INT_MASK ... TEGRA210_MIXER_TX1_ADDER_CONFIG:
case TEGRA210_MIXER_ENABLE ... TEGRA210_MIXER_CG:
case TEGRA210_MIXER_GAIN_CFG_RAM_CTRL ... TEGRA210_MIXER_CTRL:
return true;
default:
return false;
}
}
static bool tegra210_mixer_rd_reg(struct device *dev,
unsigned int reg)
{
if (reg < TEGRA210_MIXER_RX_LIMIT)
reg = MIXER_REG_BASE(reg);
else if (reg < TEGRA210_MIXER_TX_LIMIT)
reg = MIXER_REG_BASE(reg) + TEGRA210_MIXER_TX1_ENABLE;
switch (reg) {
case TEGRA210_MIXER_RX1_SOFT_RESET ... TEGRA210_MIXER_RX1_SAMPLE_COUNT:
case TEGRA210_MIXER_TX1_ENABLE ... TEGRA210_MIXER_TX1_ADDER_CONFIG:
case TEGRA210_MIXER_ENABLE ... TEGRA210_MIXER_CTRL:
return true;
default:
return false;
}
}
static bool tegra210_mixer_volatile_reg(struct device *dev,
unsigned int reg)
{
if (reg < TEGRA210_MIXER_RX_LIMIT)
reg = MIXER_REG_BASE(reg);
else if (reg < TEGRA210_MIXER_TX_LIMIT)
reg = MIXER_REG_BASE(reg) + TEGRA210_MIXER_TX1_ENABLE;
switch (reg) {
case TEGRA210_MIXER_RX1_SOFT_RESET:
case TEGRA210_MIXER_RX1_STATUS:
case TEGRA210_MIXER_TX1_SOFT_RESET:
case TEGRA210_MIXER_TX1_STATUS:
case TEGRA210_MIXER_TX1_INT_STATUS:
case TEGRA210_MIXER_TX1_INT_SET:
case TEGRA210_MIXER_SOFT_RESET:
case TEGRA210_MIXER_STATUS:
case TEGRA210_MIXER_INT_STATUS:
case TEGRA210_MIXER_GAIN_CFG_RAM_CTRL:
case TEGRA210_MIXER_GAIN_CFG_RAM_DATA:
case TEGRA210_MIXER_PEAKM_RAM_CTRL:
case TEGRA210_MIXER_PEAKM_RAM_DATA:
case TEGRA210_MIXER_RX1_SAMPLE_COUNT:
return true;
default:
return false;
}
}
static bool tegra210_mixer_precious_reg(struct device *dev,
unsigned int reg)
{
switch (reg) {
case TEGRA210_MIXER_GAIN_CFG_RAM_DATA:
case TEGRA210_MIXER_PEAKM_RAM_DATA:
return true;
default:
return false;
}
}
static const struct regmap_config tegra210_mixer_regmap_config = {
.reg_bits = 32,
.reg_stride = 4,
.val_bits = 32,
.max_register = TEGRA210_MIXER_CTRL,
.writeable_reg = tegra210_mixer_wr_reg,
.readable_reg = tegra210_mixer_rd_reg,
.volatile_reg = tegra210_mixer_volatile_reg,
.precious_reg = tegra210_mixer_precious_reg,
.reg_defaults = tegra210_mixer_reg_defaults,
.num_reg_defaults = ARRAY_SIZE(tegra210_mixer_reg_defaults),
.reg_default_cb = regmap_default_zero_cb,
.cache_type = REGCACHE_FLAT,
};
static const struct of_device_id tegra210_mixer_of_match[] = {
{ .compatible = "nvidia,tegra210-amixer" },
{},
};
MODULE_DEVICE_TABLE(of, tegra210_mixer_of_match);
static int tegra210_mixer_platform_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct tegra210_mixer *mixer;
void __iomem *regs;
int err, i;
mixer = devm_kzalloc(dev, sizeof(*mixer), GFP_KERNEL);
if (!mixer)
return -ENOMEM;
dev_set_drvdata(dev, mixer);
/* Use default gain value for all MIXER inputs */
for (i = 0; i < TEGRA210_MIXER_RX_MAX; i++) {
mixer->gain_value[i] = gain_params.gain_value;
mixer->fade_gain[i] = gain_params.gain_value;
mixer->duration[i] = gain_params.duration[DURATION_N3_ID];
}
regs = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(regs))
return PTR_ERR(regs);
mixer->regmap = devm_regmap_init_mmio(dev, regs,
&tegra210_mixer_regmap_config);
if (IS_ERR(mixer->regmap))
return dev_err_probe(dev, PTR_ERR(mixer->regmap),
"regmap init failed\n");
regcache_cache_only(mixer->regmap, true);
err = devm_snd_soc_register_component(dev, &tegra210_mixer_cmpnt,
tegra210_mixer_dais,
ARRAY_SIZE(tegra210_mixer_dais));
if (err)
return dev_err_probe(dev, err,
"can't register MIXER component\n");
pm_runtime_enable(dev);
return 0;
}
static void tegra210_mixer_platform_remove(struct platform_device *pdev)
{
pm_runtime_disable(&pdev->dev);
}
static const struct dev_pm_ops tegra210_mixer_pm_ops = {
RUNTIME_PM_OPS(tegra210_mixer_runtime_suspend,
tegra210_mixer_runtime_resume, NULL)
SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume)
};
static struct platform_driver tegra210_mixer_driver = {
.driver = {
.name = "tegra210_mixer",
.of_match_table = tegra210_mixer_of_match,
.pm = pm_ptr(&tegra210_mixer_pm_ops),
},
.probe = tegra210_mixer_platform_probe,
.remove = tegra210_mixer_platform_remove,
};
module_platform_driver(tegra210_mixer_driver);
MODULE_AUTHOR("Arun Shamanna Lakshmi <aruns@nvidia.com>");
MODULE_DESCRIPTION("Tegra210 MIXER ASoC driver");
MODULE_LICENSE("GPL v2");
@@ -0,0 +1,119 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* tegra210_mixer.h - Definitions for Tegra210 MIXER driver
*
* Copyright (c) 2021, NVIDIA CORPORATION. All rights reserved.
*
*/
#ifndef __TEGRA210_MIXER_H__
#define __TEGRA210_MIXER_H__
/* XBAR_RX related MIXER offsets */
#define TEGRA210_MIXER_RX1_SOFT_RESET 0x04
#define TEGRA210_MIXER_RX1_STATUS 0x10
#define TEGRA210_MIXER_RX1_CIF_CTRL 0x24
#define TEGRA210_MIXER_RX1_CTRL 0x28
#define TEGRA210_MIXER_RX1_PEAK_CTRL 0x2c
#define TEGRA210_MIXER_RX1_SAMPLE_COUNT 0x30
/* XBAR_TX related MIXER offsets */
#define TEGRA210_MIXER_TX1_ENABLE 0x280
#define TEGRA210_MIXER_TX1_SOFT_RESET 0x284
#define TEGRA210_MIXER_TX1_STATUS 0x290
#define TEGRA210_MIXER_TX1_INT_STATUS 0x294
#define TEGRA210_MIXER_TX1_INT_MASK 0x298
#define TEGRA210_MIXER_TX1_INT_SET 0x29c
#define TEGRA210_MIXER_TX1_INT_CLEAR 0x2a0
#define TEGRA210_MIXER_TX1_CIF_CTRL 0x2a4
#define TEGRA210_MIXER_TX1_ADDER_CONFIG 0x2a8
/* MIXER related offsets */
#define TEGRA210_MIXER_ENABLE 0x400
#define TEGRA210_MIXER_SOFT_RESET 0x404
#define TEGRA210_MIXER_CG 0x408
#define TEGRA210_MIXER_STATUS 0x410
#define TEGRA210_MIXER_INT_STATUS 0x414
#define TEGRA210_MIXER_GAIN_CFG_RAM_CTRL 0x42c
#define TEGRA210_MIXER_GAIN_CFG_RAM_DATA 0x430
#define TEGRA210_MIXER_PEAKM_RAM_CTRL 0x434
#define TEGRA210_MIXER_PEAKM_RAM_DATA 0x438
#define TEGRA210_MIXER_CTRL 0x43c
#define TEGRA210_MIXER_TX2_ADDER_CONFIG (TEGRA210_MIXER_TX1_ADDER_CONFIG + TEGRA210_MIXER_REG_STRIDE)
#define TEGRA210_MIXER_TX3_ADDER_CONFIG (TEGRA210_MIXER_TX2_ADDER_CONFIG + TEGRA210_MIXER_REG_STRIDE)
#define TEGRA210_MIXER_TX4_ADDER_CONFIG (TEGRA210_MIXER_TX3_ADDER_CONFIG + TEGRA210_MIXER_REG_STRIDE)
#define TEGRA210_MIXER_TX5_ADDER_CONFIG (TEGRA210_MIXER_TX4_ADDER_CONFIG + TEGRA210_MIXER_REG_STRIDE)
#define TEGRA210_MIXER_TX2_ENABLE (TEGRA210_MIXER_TX1_ENABLE + TEGRA210_MIXER_REG_STRIDE)
#define TEGRA210_MIXER_TX3_ENABLE (TEGRA210_MIXER_TX2_ENABLE + TEGRA210_MIXER_REG_STRIDE)
#define TEGRA210_MIXER_TX4_ENABLE (TEGRA210_MIXER_TX3_ENABLE + TEGRA210_MIXER_REG_STRIDE)
#define TEGRA210_MIXER_TX5_ENABLE (TEGRA210_MIXER_TX4_ENABLE + TEGRA210_MIXER_REG_STRIDE)
/* Fields in TEGRA210_MIXER_ENABLE */
#define TEGRA210_MIXER_ENABLE_SHIFT 0
#define TEGRA210_MIXER_ENABLE_MASK (1 << TEGRA210_MIXER_ENABLE_SHIFT)
#define TEGRA210_MIXER_EN (1 << TEGRA210_MIXER_ENABLE_SHIFT)
/* Fields in TEGRA210_MIXER_GAIN_CFG_RAM_CTRL */
#define TEGRA210_MIXER_GAIN_CFG_RAM_ADDR_0 0x0
#define TEGRA210_MIXER_GAIN_CFG_RAM_ADDR_STRIDE 0x10
#define TEGRA210_MIXER_GAIN_CFG_RAM_RW_SHIFT 14
#define TEGRA210_MIXER_GAIN_CFG_RAM_RW_MASK (1 << TEGRA210_MIXER_GAIN_CFG_RAM_RW_SHIFT)
#define TEGRA210_MIXER_GAIN_CFG_RAM_RW_WRITE (1 << TEGRA210_MIXER_GAIN_CFG_RAM_RW_SHIFT)
#define TEGRA210_MIXER_GAIN_CFG_RAM_ADDR_INIT_EN_SHIFT 13
#define TEGRA210_MIXER_GAIN_CFG_RAM_ADDR_INIT_EN_MASK (1 << TEGRA210_MIXER_GAIN_CFG_RAM_ADDR_INIT_EN_SHIFT)
#define TEGRA210_MIXER_GAIN_CFG_RAM_ADDR_INIT_EN (1 << TEGRA210_MIXER_GAIN_CFG_RAM_ADDR_INIT_EN_SHIFT)
#define TEGRA210_MIXER_GAIN_CFG_RAM_SEQ_ACCESS_EN_SHIFT 12
#define TEGRA210_MIXER_GAIN_CFG_RAM_SEQ_ACCESS_EN_MASK (1 << TEGRA210_MIXER_GAIN_CFG_RAM_SEQ_ACCESS_EN_SHIFT)
#define TEGRA210_MIXER_GAIN_CFG_RAM_SEQ_ACCESS_EN (1 << TEGRA210_MIXER_GAIN_CFG_RAM_SEQ_ACCESS_EN_SHIFT)
#define TEGRA210_MIXER_GAIN_CFG_RAM_ADDR_SHIFT 0
#define TEGRA210_MIXER_GAIN_CFG_RAM_ADDR_MASK (0x1ff << TEGRA210_MIXER_GAIN_CFG_RAM_ADDR_SHIFT)
#define TEGRA210_MIXER_REG_STRIDE 0x40
#define TEGRA210_MIXER_RX_MAX 10
#define TEGRA210_MIXER_RX_LIMIT (TEGRA210_MIXER_RX_MAX * TEGRA210_MIXER_REG_STRIDE)
#define TEGRA210_MIXER_TX_MAX 5
#define TEGRA210_MIXER_TX_LIMIT (TEGRA210_MIXER_RX_LIMIT + (TEGRA210_MIXER_TX_MAX * TEGRA210_MIXER_REG_STRIDE))
#define TEGRA210_MIXER_SAMPLE_COUNT_SHIFT 24
#define TEGRA210_MIXER_SAMPLE_COUNT_ENABLE BIT(TEGRA210_MIXER_SAMPLE_COUNT_SHIFT)
#define REG_CFG_DONE_TRIGGER 0xf
#define VAL_CFG_DONE_TRIGGER 0x1
#define NUM_GAIN_POLY_COEFFS 9
#define TEGRA210_MIXER_GAIN_MAX 0x20000
#define TEGRA210_MIXER_PRESCALAR 6
#define TEGRA210_MIXER_FADE_DURATION_MIN \
(BIT(TEGRA210_MIXER_PRESCALAR - 1) + 1)
#define TEGRA210_MIXER_FADE_DURATION_MAX 0x7fffffff
#define TEGRA210_MIXER_FADE_IDLE 0
#define TEGRA210_MIXER_FADE_ACTIVE 1
enum {
DURATION_N3_ID = 2,
DURATION_INV_N3_ID,
NUM_DURATION_PARMS,
};
struct tegra210_mixer_gain_params {
int poly_coeff[NUM_GAIN_POLY_COEFFS];
int gain_value;
int duration[NUM_DURATION_PARMS];
};
struct tegra210_mixer {
int gain_value[TEGRA210_MIXER_RX_MAX];
int fade_gain[TEGRA210_MIXER_RX_MAX];
u32 duration[TEGRA210_MIXER_RX_MAX];
bool in_fade[TEGRA210_MIXER_RX_MAX];
bool fade_pending[TEGRA210_MIXER_RX_MAX];
struct regmap *regmap;
};
#endif
+780
View File
@@ -0,0 +1,780 @@
// SPDX-License-Identifier: GPL-2.0-only
// SPDX-FileCopyrightText: Copyright (c) 2021-2024 NVIDIA CORPORATION & AFFILIATES.
// All rights reserved.
//
// tegra210_mvc.c - Tegra210 MVC driver
#include <linux/clk.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include "tegra210_mvc.h"
#include "tegra_cif.h"
static const struct reg_default tegra210_mvc_reg_defaults[] = {
{ TEGRA210_MVC_RX_INT_MASK, 0x00000001},
{ TEGRA210_MVC_RX_CIF_CTRL, 0x00007700},
{ TEGRA210_MVC_TX_INT_MASK, 0x00000001},
{ TEGRA210_MVC_TX_CIF_CTRL, 0x00007700},
{ TEGRA210_MVC_CG, 0x1},
{ TEGRA210_MVC_CTRL, TEGRA210_MVC_CTRL_DEFAULT},
{ TEGRA210_MVC_INIT_VOL, 0x00800000},
{ TEGRA210_MVC_TARGET_VOL, 0x00800000},
{ TEGRA210_MVC_DURATION, 0x000012c0},
{ TEGRA210_MVC_DURATION_INV, 0x0006d3a0},
{ TEGRA210_MVC_POLY_N1, 0x0000007d},
{ TEGRA210_MVC_POLY_N2, 0x00000271},
{ TEGRA210_MVC_PEAK_CTRL, 0x000012c0},
{ TEGRA210_MVC_CFG_RAM_CTRL, 0x00004000},
};
static const struct tegra210_mvc_gain_params gain_params = {
.poly_coeff = { 23738319, 659403, -3680,
15546680, 2530732, -120985,
12048422, 5527252, -785042 },
.poly_n1 = 16,
.poly_n2 = 63,
.duration = 150,
.duration_inv = 14316558,
};
static int tegra210_mvc_runtime_suspend(struct device *dev)
{
struct tegra210_mvc *mvc = dev_get_drvdata(dev);
regmap_read(mvc->regmap, TEGRA210_MVC_CTRL, &(mvc->ctrl_value));
regcache_cache_only(mvc->regmap, true);
regcache_mark_dirty(mvc->regmap);
return 0;
}
static int tegra210_mvc_runtime_resume(struct device *dev)
{
struct tegra210_mvc *mvc = dev_get_drvdata(dev);
regcache_cache_only(mvc->regmap, false);
regcache_sync(mvc->regmap);
regmap_write(mvc->regmap, TEGRA210_MVC_CTRL, mvc->ctrl_value);
regmap_update_bits(mvc->regmap,
TEGRA210_MVC_SWITCH,
TEGRA210_MVC_VOLUME_SWITCH_MASK,
TEGRA210_MVC_VOLUME_SWITCH_TRIGGER);
return 0;
}
static void tegra210_mvc_write_ram(struct regmap *regmap)
{
int i;
regmap_write(regmap, TEGRA210_MVC_CFG_RAM_CTRL,
TEGRA210_MVC_CFG_RAM_CTRL_SEQ_ACCESS_EN |
TEGRA210_MVC_CFG_RAM_CTRL_ADDR_INIT_EN |
TEGRA210_MVC_CFG_RAM_CTRL_RW_WRITE);
for (i = 0; i < NUM_GAIN_POLY_COEFFS; i++)
regmap_write(regmap, TEGRA210_MVC_CFG_RAM_DATA,
gain_params.poly_coeff[i]);
}
static void tegra210_mvc_conv_vol(struct tegra210_mvc *mvc, u8 chan, s32 val)
{
/*
* Volume control read from mixer control is with
* 100x scaling; for CURVE_POLY the reg range
* is 0-100 (linear, Q24) and for CURVE_LINEAR
* it is -120dB to +40dB (Q8)
*/
if (mvc->curve_type == CURVE_POLY) {
if (val > 10000)
val = 10000;
mvc->volume[chan] = ((val * (1<<8)) / 100) << 16;
} else {
val -= 12000;
mvc->volume[chan] = (val * (1<<8)) / 100;
}
}
static u32 tegra210_mvc_get_ctrl_reg(struct snd_kcontrol *kcontrol)
{
struct snd_soc_component *cmpnt = snd_kcontrol_chip(kcontrol);
struct tegra210_mvc *mvc = snd_soc_component_get_drvdata(cmpnt);
u32 val;
pm_runtime_get_sync(cmpnt->dev);
regmap_read(mvc->regmap, TEGRA210_MVC_CTRL, &val);
pm_runtime_put(cmpnt->dev);
return val;
}
static int tegra210_mvc_get_mute(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
u32 val = tegra210_mvc_get_ctrl_reg(kcontrol);
u8 mute_mask = TEGRA210_GET_MUTE_VAL(val);
/*
* If per channel control is enabled, then return
* exact mute/unmute setting of all channels.
*
* Else report setting based on CH0 bit to reflect
* the correct HW state.
*/
if (val & TEGRA210_MVC_PER_CHAN_CTRL_EN) {
ucontrol->value.integer.value[0] = mute_mask;
} else {
if (mute_mask & TEGRA210_MVC_CH0_MUTE_EN)
ucontrol->value.integer.value[0] =
TEGRA210_MUTE_MASK_EN;
else
ucontrol->value.integer.value[0] = 0;
}
return 0;
}
static int tegra210_mvc_get_master_mute(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
u32 val = tegra210_mvc_get_ctrl_reg(kcontrol);
u8 mute_mask = TEGRA210_GET_MUTE_VAL(val);
/*
* If per channel control is disabled, then return
* master mute/unmute setting based on CH0 bit.
*
* Else report settings based on state of all
* channels.
*/
if (!(val & TEGRA210_MVC_PER_CHAN_CTRL_EN)) {
ucontrol->value.integer.value[0] =
mute_mask & TEGRA210_MVC_CH0_MUTE_EN;
} else {
if (mute_mask == TEGRA210_MUTE_MASK_EN)
ucontrol->value.integer.value[0] =
TEGRA210_MVC_CH0_MUTE_EN;
else
ucontrol->value.integer.value[0] = 0;
}
return 0;
}
static int tegra210_mvc_volume_switch_timeout(struct snd_soc_component *cmpnt)
{
struct tegra210_mvc *mvc = snd_soc_component_get_drvdata(cmpnt);
u32 value;
int err;
err = regmap_read_poll_timeout(mvc->regmap, TEGRA210_MVC_SWITCH,
value, !(value & TEGRA210_MVC_VOLUME_SWITCH_MASK),
10, 10000);
if (err < 0)
dev_err(cmpnt->dev,
"Volume switch trigger is still active, err = %d\n",
err);
return err;
}
static int tegra210_mvc_update_mute(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol,
bool per_chan_ctrl)
{
struct snd_soc_component *cmpnt = snd_kcontrol_chip(kcontrol);
struct tegra210_mvc *mvc = snd_soc_component_get_drvdata(cmpnt);
u32 mute_val = ucontrol->value.integer.value[0];
u32 per_ch_ctrl_val;
bool change = false;
int err;
pm_runtime_get_sync(cmpnt->dev);
err = tegra210_mvc_volume_switch_timeout(cmpnt);
if (err < 0)
goto end;
if (per_chan_ctrl) {
per_ch_ctrl_val = TEGRA210_MVC_PER_CHAN_CTRL_EN;
} else {
per_ch_ctrl_val = 0;
if (mute_val)
mute_val = TEGRA210_MUTE_MASK_EN;
}
regmap_update_bits_check(mvc->regmap, TEGRA210_MVC_CTRL,
TEGRA210_MVC_MUTE_MASK,
mute_val << TEGRA210_MVC_MUTE_SHIFT,
&change);
if (change) {
regmap_update_bits(mvc->regmap, TEGRA210_MVC_CTRL,
TEGRA210_MVC_PER_CHAN_CTRL_EN_MASK,
per_ch_ctrl_val);
regmap_update_bits(mvc->regmap, TEGRA210_MVC_SWITCH,
TEGRA210_MVC_VOLUME_SWITCH_MASK,
TEGRA210_MVC_VOLUME_SWITCH_TRIGGER);
}
end:
pm_runtime_put(cmpnt->dev);
if (err < 0)
return err;
if (change)
return 1;
return 0;
}
static int tegra210_mvc_put_mute(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
return tegra210_mvc_update_mute(kcontrol, ucontrol, true);
}
static int tegra210_mvc_put_master_mute(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
return tegra210_mvc_update_mute(kcontrol, ucontrol, false);
}
static int tegra210_mvc_get_vol(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct soc_mixer_control *mc =
(struct soc_mixer_control *)kcontrol->private_value;
struct snd_soc_component *cmpnt = snd_kcontrol_chip(kcontrol);
struct tegra210_mvc *mvc = snd_soc_component_get_drvdata(cmpnt);
u8 chan = TEGRA210_MVC_GET_CHAN(mc->reg, TEGRA210_MVC_TARGET_VOL);
s32 val = mvc->volume[chan];
if (mvc->curve_type == CURVE_POLY) {
val = ((val >> 16) * 100) >> 8;
} else {
val = (val * 100) >> 8;
val += 12000;
}
ucontrol->value.integer.value[0] = val;
return 0;
}
static int tegra210_mvc_get_master_vol(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
return tegra210_mvc_get_vol(kcontrol, ucontrol);
}
static int tegra210_mvc_update_vol(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol,
bool per_ch_enable)
{
struct soc_mixer_control *mc =
(struct soc_mixer_control *)kcontrol->private_value;
struct snd_soc_component *cmpnt = snd_kcontrol_chip(kcontrol);
struct tegra210_mvc *mvc = snd_soc_component_get_drvdata(cmpnt);
u8 chan = TEGRA210_MVC_GET_CHAN(mc->reg, TEGRA210_MVC_TARGET_VOL);
int old_volume = mvc->volume[chan];
int err, i;
pm_runtime_get_sync(cmpnt->dev);
err = tegra210_mvc_volume_switch_timeout(cmpnt);
if (err < 0)
goto end;
tegra210_mvc_conv_vol(mvc, chan, ucontrol->value.integer.value[0]);
if (mvc->volume[chan] == old_volume) {
err = 0;
goto end;
}
if (per_ch_enable) {
regmap_update_bits(mvc->regmap, TEGRA210_MVC_CTRL,
TEGRA210_MVC_PER_CHAN_CTRL_EN_MASK,
TEGRA210_MVC_PER_CHAN_CTRL_EN);
} else {
regmap_update_bits(mvc->regmap, TEGRA210_MVC_CTRL,
TEGRA210_MVC_PER_CHAN_CTRL_EN_MASK, 0);
for (i = 1; i < TEGRA210_MVC_MAX_CHAN_COUNT; i++)
mvc->volume[i] = mvc->volume[chan];
}
/* Configure init volume same as target volume */
regmap_write(mvc->regmap,
TEGRA210_MVC_REG_OFFSET(TEGRA210_MVC_INIT_VOL, chan),
mvc->volume[chan]);
regmap_write(mvc->regmap, mc->reg, mvc->volume[chan]);
regmap_update_bits(mvc->regmap, TEGRA210_MVC_SWITCH,
TEGRA210_MVC_VOLUME_SWITCH_MASK,
TEGRA210_MVC_VOLUME_SWITCH_TRIGGER);
err = 1;
end:
pm_runtime_put(cmpnt->dev);
return err;
}
static int tegra210_mvc_put_vol(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
return tegra210_mvc_update_vol(kcontrol, ucontrol, true);
}
static int tegra210_mvc_put_master_vol(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
return tegra210_mvc_update_vol(kcontrol, ucontrol, false);
}
static void tegra210_mvc_reset_vol_settings(struct tegra210_mvc *mvc,
struct device *dev)
{
int i;
/* Change volume to default init for new curve type */
if (mvc->curve_type == CURVE_POLY) {
for (i = 0; i < TEGRA210_MVC_MAX_CHAN_COUNT; i++)
mvc->volume[i] = TEGRA210_MVC_INIT_VOL_DEFAULT_POLY;
} else {
for (i = 0; i < TEGRA210_MVC_MAX_CHAN_COUNT; i++)
mvc->volume[i] = TEGRA210_MVC_INIT_VOL_DEFAULT_LINEAR;
}
pm_runtime_get_sync(dev);
/* Program curve type */
regmap_update_bits(mvc->regmap, TEGRA210_MVC_CTRL,
TEGRA210_MVC_CURVE_TYPE_MASK,
mvc->curve_type <<
TEGRA210_MVC_CURVE_TYPE_SHIFT);
/* Init volume for all channels */
for (i = 0; i < TEGRA210_MVC_MAX_CHAN_COUNT; i++) {
regmap_write(mvc->regmap,
TEGRA210_MVC_REG_OFFSET(TEGRA210_MVC_INIT_VOL, i),
mvc->volume[i]);
regmap_write(mvc->regmap,
TEGRA210_MVC_REG_OFFSET(TEGRA210_MVC_TARGET_VOL, i),
mvc->volume[i]);
}
/* Trigger volume switch */
regmap_update_bits(mvc->regmap, TEGRA210_MVC_SWITCH,
TEGRA210_MVC_VOLUME_SWITCH_MASK,
TEGRA210_MVC_VOLUME_SWITCH_TRIGGER);
pm_runtime_put(dev);
}
static int tegra210_mvc_get_curve_type(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *cmpnt = snd_kcontrol_chip(kcontrol);
struct tegra210_mvc *mvc = snd_soc_component_get_drvdata(cmpnt);
ucontrol->value.enumerated.item[0] = mvc->curve_type;
return 0;
}
static int tegra210_mvc_put_curve_type(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *cmpnt = snd_kcontrol_chip(kcontrol);
struct tegra210_mvc *mvc = snd_soc_component_get_drvdata(cmpnt);
unsigned int value;
regmap_read(mvc->regmap, TEGRA210_MVC_ENABLE, &value);
if (value & TEGRA210_MVC_EN) {
dev_err(cmpnt->dev,
"Curve type can't be set when MVC is running\n");
return -EINVAL;
}
if (mvc->curve_type == ucontrol->value.enumerated.item[0])
return 0;
mvc->curve_type = ucontrol->value.enumerated.item[0];
tegra210_mvc_reset_vol_settings(mvc, cmpnt->dev);
return 1;
}
static int tegra210_mvc_set_audio_cif(struct tegra210_mvc *mvc,
struct snd_pcm_hw_params *params,
unsigned int reg)
{
unsigned int channels, audio_bits;
struct tegra_cif_conf cif_conf;
memset(&cif_conf, 0, sizeof(struct tegra_cif_conf));
channels = params_channels(params);
switch (params_format(params)) {
case SNDRV_PCM_FORMAT_S8:
audio_bits = TEGRA_ACIF_BITS_8;
break;
case SNDRV_PCM_FORMAT_S16_LE:
audio_bits = TEGRA_ACIF_BITS_16;
break;
case SNDRV_PCM_FORMAT_S24_LE:
case SNDRV_PCM_FORMAT_S32_LE:
audio_bits = TEGRA_ACIF_BITS_32;
break;
default:
return -EINVAL;
}
cif_conf.audio_ch = channels;
cif_conf.client_ch = channels;
cif_conf.audio_bits = audio_bits;
cif_conf.client_bits = audio_bits;
tegra_set_cif(mvc->regmap, reg, &cif_conf);
return 0;
}
static int tegra210_mvc_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_mvc *mvc = snd_soc_dai_get_drvdata(dai);
int err, val;
/*
* Soft Reset: Below performs module soft reset which clears
* all FSM logic, flushes flow control of FIFO and resets the
* state register. It also brings module back to disabled
* state (without flushing the data in the pipe).
*/
regmap_write(mvc->regmap, TEGRA210_MVC_SOFT_RESET, 1);
err = regmap_read_poll_timeout(mvc->regmap, TEGRA210_MVC_SOFT_RESET,
val, !val, 10, 10000);
if (err < 0) {
dev_err(dev, "SW reset failed, err = %d\n", err);
return err;
}
/* Set RX CIF */
err = tegra210_mvc_set_audio_cif(mvc, params, TEGRA210_MVC_RX_CIF_CTRL);
if (err) {
dev_err(dev, "Can't set MVC RX CIF: %d\n", err);
return err;
}
/* Set TX CIF */
err = tegra210_mvc_set_audio_cif(mvc, params, TEGRA210_MVC_TX_CIF_CTRL);
if (err) {
dev_err(dev, "Can't set MVC TX CIF: %d\n", err);
return err;
}
tegra210_mvc_write_ram(mvc->regmap);
/* Program poly_n1, poly_n2, duration */
regmap_write(mvc->regmap, TEGRA210_MVC_POLY_N1, gain_params.poly_n1);
regmap_write(mvc->regmap, TEGRA210_MVC_POLY_N2, gain_params.poly_n2);
regmap_write(mvc->regmap, TEGRA210_MVC_DURATION, gain_params.duration);
/* Program duration_inv */
regmap_write(mvc->regmap, TEGRA210_MVC_DURATION_INV,
gain_params.duration_inv);
return 0;
}
static const struct snd_soc_dai_ops tegra210_mvc_dai_ops = {
.hw_params = tegra210_mvc_hw_params,
};
static const char * const tegra210_mvc_curve_type_text[] = {
"Poly",
"Linear",
};
static const struct soc_enum tegra210_mvc_curve_type_ctrl =
SOC_ENUM_SINGLE_EXT(2, tegra210_mvc_curve_type_text);
#define TEGRA210_MVC_VOL_CTRL(chan) \
SOC_SINGLE_EXT("Channel" #chan " Volume", \
TEGRA210_MVC_REG_OFFSET(TEGRA210_MVC_TARGET_VOL, \
(chan - 1)), \
0, 16000, 0, tegra210_mvc_get_vol, \
tegra210_mvc_put_vol)
static const struct snd_kcontrol_new tegra210_mvc_vol_ctrl[] = {
/* Per channel volume control */
TEGRA210_MVC_VOL_CTRL(1),
TEGRA210_MVC_VOL_CTRL(2),
TEGRA210_MVC_VOL_CTRL(3),
TEGRA210_MVC_VOL_CTRL(4),
TEGRA210_MVC_VOL_CTRL(5),
TEGRA210_MVC_VOL_CTRL(6),
TEGRA210_MVC_VOL_CTRL(7),
TEGRA210_MVC_VOL_CTRL(8),
/* Per channel mute */
SOC_SINGLE_EXT("Per Chan Mute Mask",
TEGRA210_MVC_CTRL, 0, TEGRA210_MUTE_MASK_EN, 0,
tegra210_mvc_get_mute, tegra210_mvc_put_mute),
/* Master volume */
SOC_SINGLE_EXT("Volume", TEGRA210_MVC_TARGET_VOL, 0, 16000, 0,
tegra210_mvc_get_master_vol,
tegra210_mvc_put_master_vol),
/* Master mute */
SOC_SINGLE_EXT("Mute", TEGRA210_MVC_CTRL, 0, 1, 0,
tegra210_mvc_get_master_mute,
tegra210_mvc_put_master_mute),
SOC_ENUM_EXT("Curve Type", tegra210_mvc_curve_type_ctrl,
tegra210_mvc_get_curve_type, tegra210_mvc_put_curve_type),
};
static struct snd_soc_dai_driver tegra210_mvc_dais[] = {
/* Input */
{
.name = "MVC-RX-CIF",
.playback = {
.stream_name = "RX-CIF-Playback",
.channels_min = 1,
.channels_max = 8,
.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 = "RX-CIF-Capture",
.channels_min = 1,
.channels_max = 8,
.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,
},
},
/* Output */
{
.name = "MVC-TX-CIF",
.playback = {
.stream_name = "TX-CIF-Playback",
.channels_min = 1,
.channels_max = 8,
.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 = "TX-CIF-Capture",
.channels_min = 1,
.channels_max = 8,
.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,
},
.ops = &tegra210_mvc_dai_ops,
}
};
static const struct snd_soc_dapm_widget tegra210_mvc_widgets[] = {
SND_SOC_DAPM_AIF_IN("RX", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_OUT("TX", NULL, 0, TEGRA210_MVC_ENABLE,
TEGRA210_MVC_EN_SHIFT, 0),
};
#define MVC_ROUTES(sname) \
{ "RX XBAR-" sname, NULL, "XBAR-TX" }, \
{ "RX-CIF-" sname, NULL, "RX XBAR-" sname }, \
{ "RX", NULL, "RX-CIF-" sname }, \
{ "TX-CIF-" sname, NULL, "TX" }, \
{ "TX XBAR-" sname, NULL, "TX-CIF-" sname }, \
{ "XBAR-RX", NULL, "TX XBAR-" sname }
static const struct snd_soc_dapm_route tegra210_mvc_routes[] = {
{ "TX", NULL, "RX" },
MVC_ROUTES("Playback"),
MVC_ROUTES("Capture"),
};
static const struct snd_soc_component_driver tegra210_mvc_cmpnt = {
.dapm_widgets = tegra210_mvc_widgets,
.num_dapm_widgets = ARRAY_SIZE(tegra210_mvc_widgets),
.dapm_routes = tegra210_mvc_routes,
.num_dapm_routes = ARRAY_SIZE(tegra210_mvc_routes),
.controls = tegra210_mvc_vol_ctrl,
.num_controls = ARRAY_SIZE(tegra210_mvc_vol_ctrl),
};
static bool tegra210_mvc_rd_reg(struct device *dev, unsigned int reg)
{
switch (reg) {
case TEGRA210_MVC_RX_STATUS ... TEGRA210_MVC_CONFIG_ERR_TYPE:
return true;
default:
return false;
};
}
static bool tegra210_mvc_wr_reg(struct device *dev, unsigned int reg)
{
switch (reg) {
case TEGRA210_MVC_RX_INT_MASK ... TEGRA210_MVC_RX_CIF_CTRL:
case TEGRA210_MVC_TX_INT_MASK ... TEGRA210_MVC_TX_CIF_CTRL:
case TEGRA210_MVC_ENABLE ... TEGRA210_MVC_CG:
case TEGRA210_MVC_CTRL ... TEGRA210_MVC_CFG_RAM_DATA:
return true;
default:
return false;
}
}
static bool tegra210_mvc_volatile_reg(struct device *dev, unsigned int reg)
{
switch (reg) {
case TEGRA210_MVC_RX_STATUS:
case TEGRA210_MVC_RX_INT_STATUS:
case TEGRA210_MVC_RX_INT_SET:
case TEGRA210_MVC_TX_STATUS:
case TEGRA210_MVC_TX_INT_STATUS:
case TEGRA210_MVC_TX_INT_SET:
case TEGRA210_MVC_SOFT_RESET:
case TEGRA210_MVC_STATUS:
case TEGRA210_MVC_INT_STATUS:
case TEGRA210_MVC_SWITCH:
case TEGRA210_MVC_CFG_RAM_CTRL:
case TEGRA210_MVC_CFG_RAM_DATA:
case TEGRA210_MVC_PEAK_VALUE:
case TEGRA210_MVC_CTRL:
return true;
default:
return false;
}
}
static const struct regmap_config tegra210_mvc_regmap_config = {
.reg_bits = 32,
.reg_stride = 4,
.val_bits = 32,
.max_register = TEGRA210_MVC_CONFIG_ERR_TYPE,
.writeable_reg = tegra210_mvc_wr_reg,
.readable_reg = tegra210_mvc_rd_reg,
.volatile_reg = tegra210_mvc_volatile_reg,
.reg_defaults = tegra210_mvc_reg_defaults,
.num_reg_defaults = ARRAY_SIZE(tegra210_mvc_reg_defaults),
.reg_default_cb = regmap_default_zero_cb,
.cache_type = REGCACHE_FLAT,
};
static const struct of_device_id tegra210_mvc_of_match[] = {
{ .compatible = "nvidia,tegra210-mvc" },
{},
};
MODULE_DEVICE_TABLE(of, tegra210_mvc_of_match);
static int tegra210_mvc_platform_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct tegra210_mvc *mvc;
void __iomem *regs;
int err;
mvc = devm_kzalloc(dev, sizeof(*mvc), GFP_KERNEL);
if (!mvc)
return -ENOMEM;
dev_set_drvdata(dev, mvc);
mvc->curve_type = CURVE_LINEAR;
mvc->ctrl_value = TEGRA210_MVC_CTRL_DEFAULT;
regs = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(regs))
return PTR_ERR(regs);
mvc->regmap = devm_regmap_init_mmio(dev, regs,
&tegra210_mvc_regmap_config);
if (IS_ERR(mvc->regmap))
return dev_err_probe(dev, PTR_ERR(mvc->regmap),
"regmap init failed\n");
regcache_cache_only(mvc->regmap, true);
err = devm_snd_soc_register_component(dev, &tegra210_mvc_cmpnt,
tegra210_mvc_dais,
ARRAY_SIZE(tegra210_mvc_dais));
if (err)
return dev_err_probe(dev, err,
"can't register MVC component\n");
pm_runtime_enable(dev);
tegra210_mvc_reset_vol_settings(mvc, &pdev->dev);
return 0;
}
static void tegra210_mvc_platform_remove(struct platform_device *pdev)
{
pm_runtime_disable(&pdev->dev);
}
static const struct dev_pm_ops tegra210_mvc_pm_ops = {
RUNTIME_PM_OPS(tegra210_mvc_runtime_suspend,
tegra210_mvc_runtime_resume, NULL)
SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume)
};
static struct platform_driver tegra210_mvc_driver = {
.driver = {
.name = "tegra210-mvc",
.of_match_table = tegra210_mvc_of_match,
.pm = pm_ptr(&tegra210_mvc_pm_ops),
},
.probe = tegra210_mvc_platform_probe,
.remove = tegra210_mvc_platform_remove,
};
module_platform_driver(tegra210_mvc_driver)
MODULE_AUTHOR("Arun Shamanna Lakshmi <aruns@nvidia.com>");
MODULE_DESCRIPTION("Tegra210 MVC ASoC driver");
MODULE_LICENSE("GPL v2");
+122
View File
@@ -0,0 +1,122 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* tegra210_mvc.h - Definitions for Tegra210 MVC driver
*
* Copyright (c) 2021 NVIDIA CORPORATION. All rights reserved.
*
*/
#ifndef __TEGRA210_MVC_H__
#define __TEGRA210_MVC_H__
/*
* MVC_RX registers are with respect to XBAR.
* The data comes from XBAR to MVC.
*/
#define TEGRA210_MVC_RX_STATUS 0x0c
#define TEGRA210_MVC_RX_INT_STATUS 0x10
#define TEGRA210_MVC_RX_INT_MASK 0x14
#define TEGRA210_MVC_RX_INT_SET 0x18
#define TEGRA210_MVC_RX_INT_CLEAR 0x1c
#define TEGRA210_MVC_RX_CIF_CTRL 0x20
/*
* MVC_TX registers are with respect to XBAR.
* The data goes out of MVC.
*/
#define TEGRA210_MVC_TX_STATUS 0x4c
#define TEGRA210_MVC_TX_INT_STATUS 0x50
#define TEGRA210_MVC_TX_INT_MASK 0x54
#define TEGRA210_MVC_TX_INT_SET 0x58
#define TEGRA210_MVC_TX_INT_CLEAR 0x5c
#define TEGRA210_MVC_TX_CIF_CTRL 0x60
/* Register offsets from TEGRA210_MVC*_BASE */
#define TEGRA210_MVC_ENABLE 0x80
#define TEGRA210_MVC_SOFT_RESET 0x84
#define TEGRA210_MVC_CG 0x88
#define TEGRA210_MVC_STATUS 0x90
#define TEGRA210_MVC_INT_STATUS 0x94
#define TEGRA210_MVC_CTRL 0xa8
#define TEGRA210_MVC_SWITCH 0xac
#define TEGRA210_MVC_INIT_VOL 0xb0
#define TEGRA210_MVC_TARGET_VOL 0xd0
#define TEGRA210_MVC_DURATION 0xf0
#define TEGRA210_MVC_DURATION_INV 0xf4
#define TEGRA210_MVC_POLY_N1 0xf8
#define TEGRA210_MVC_POLY_N2 0xfc
#define TEGRA210_MVC_PEAK_CTRL 0x100
#define TEGRA210_MVC_CFG_RAM_CTRL 0x104
#define TEGRA210_MVC_CFG_RAM_DATA 0x108
#define TEGRA210_MVC_PEAK_VALUE 0x10c
#define TEGRA210_MVC_CONFIG_ERR_TYPE 0x12c
/* Fields in TEGRA210_MVC_ENABLE */
#define TEGRA210_MVC_EN_SHIFT 0
#define TEGRA210_MVC_EN (1 << TEGRA210_MVC_EN_SHIFT)
#define TEGRA210_MVC_MUTE_SHIFT 8
#define TEGRA210_MUTE_MASK_EN 0xff
#define TEGRA210_MVC_MUTE_MASK (TEGRA210_MUTE_MASK_EN << TEGRA210_MVC_MUTE_SHIFT)
#define TEGRA210_MVC_MUTE_EN (TEGRA210_MUTE_MASK_EN << TEGRA210_MVC_MUTE_SHIFT)
#define TEGRA210_MVC_CH0_MUTE_EN 1
#define TEGRA210_MVC_PER_CHAN_CTRL_EN_SHIFT 30
#define TEGRA210_MVC_PER_CHAN_CTRL_EN_MASK (1 << TEGRA210_MVC_PER_CHAN_CTRL_EN_SHIFT)
#define TEGRA210_MVC_PER_CHAN_CTRL_EN (1 << TEGRA210_MVC_PER_CHAN_CTRL_EN_SHIFT)
#define TEGRA210_MVC_CURVE_TYPE_SHIFT 1
#define TEGRA210_MVC_CURVE_TYPE_MASK (1 << TEGRA210_MVC_CURVE_TYPE_SHIFT)
#define TEGRA210_MVC_VOLUME_SWITCH_SHIFT 2
#define TEGRA210_MVC_VOLUME_SWITCH_MASK (1 << TEGRA210_MVC_VOLUME_SWITCH_SHIFT)
#define TEGRA210_MVC_VOLUME_SWITCH_TRIGGER (1 << TEGRA210_MVC_VOLUME_SWITCH_SHIFT)
#define TEGRA210_MVC_CTRL_DEFAULT 0x40000003
#define TEGRA210_MVC_INIT_VOL_DEFAULT_POLY 0x01000000
#define TEGRA210_MVC_INIT_VOL_DEFAULT_LINEAR 0x00000000
/* Fields in TEGRA210_MVC ram ctrl */
#define TEGRA210_MVC_CFG_RAM_CTRL_RW_SHIFT 14
#define TEGRA210_MVC_CFG_RAM_CTRL_RW_WRITE (1 << TEGRA210_MVC_CFG_RAM_CTRL_RW_SHIFT)
#define TEGRA210_MVC_CFG_RAM_CTRL_ADDR_INIT_EN_SHIFT 13
#define TEGRA210_MVC_CFG_RAM_CTRL_ADDR_INIT_EN (1 << TEGRA210_MVC_CFG_RAM_CTRL_ADDR_INIT_EN_SHIFT)
#define TEGRA210_MVC_CFG_RAM_CTRL_SEQ_ACCESS_EN_SHIFT 12
#define TEGRA210_MVC_CFG_RAM_CTRL_SEQ_ACCESS_EN (1 << TEGRA210_MVC_CFG_RAM_CTRL_SEQ_ACCESS_EN_SHIFT)
#define TEGRA210_MVC_CFG_RAM_CTRL_ADDR_SHIFT 0
#define TEGRA210_MVC_CFG_RAM_CTRL_ADDR_MASK (0x1ff << TEGRA210_MVC_CFG_RAM_CTRL_ADDR_SHIFT)
#define REG_SIZE 4
#define TEGRA210_MVC_MAX_CHAN_COUNT 8
#define TEGRA210_MVC_REG_OFFSET(reg, i) (reg + (REG_SIZE * i))
#define TEGRA210_MVC_GET_CHAN(reg, base) (((reg) - (base)) / REG_SIZE)
#define TEGRA210_GET_MUTE_VAL(val) (((val) >> TEGRA210_MVC_MUTE_SHIFT) & TEGRA210_MUTE_MASK_EN)
#define NUM_GAIN_POLY_COEFFS 9
enum {
CURVE_POLY,
CURVE_LINEAR,
};
struct tegra210_mvc_gain_params {
int poly_coeff[NUM_GAIN_POLY_COEFFS];
int poly_n1;
int poly_n2;
int duration;
int duration_inv;
};
struct tegra210_mvc {
int volume[TEGRA210_MVC_MAX_CHAN_COUNT];
unsigned int curve_type;
unsigned int ctrl_value;
struct regmap *regmap;
};
#endif
+414
View File
@@ -0,0 +1,414 @@
// SPDX-License-Identifier: GPL-2.0-only
// SPDX-FileCopyrightText: Copyright (c) 2022-2024 NVIDIA CORPORATION & AFFILIATES.
// All rights reserved.
//
// tegra210_ope.c - Tegra210 OPE driver
#include <linux/clk.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include "tegra210_mbdrc.h"
#include "tegra210_ope.h"
#include "tegra210_peq.h"
#include "tegra_cif.h"
static const struct reg_default tegra210_ope_reg_defaults[] = {
{ TEGRA210_OPE_RX_INT_MASK, 0x00000001},
{ TEGRA210_OPE_RX_CIF_CTRL, 0x00007700},
{ TEGRA210_OPE_TX_INT_MASK, 0x00000001},
{ TEGRA210_OPE_TX_CIF_CTRL, 0x00007700},
{ TEGRA210_OPE_CG, 0x1},
};
static int tegra210_ope_set_audio_cif(struct tegra210_ope *ope,
struct snd_pcm_hw_params *params,
unsigned int reg)
{
int channels, audio_bits;
struct tegra_cif_conf cif_conf;
memset(&cif_conf, 0, sizeof(struct tegra_cif_conf));
channels = params_channels(params);
if (channels < 2)
return -EINVAL;
switch (params_format(params)) {
case SNDRV_PCM_FORMAT_S16_LE:
audio_bits = TEGRA_ACIF_BITS_16;
break;
case SNDRV_PCM_FORMAT_S24_LE:
case SNDRV_PCM_FORMAT_S32_LE:
audio_bits = TEGRA_ACIF_BITS_32;
break;
default:
return -EINVAL;
}
cif_conf.audio_ch = channels;
cif_conf.client_ch = channels;
cif_conf.audio_bits = audio_bits;
cif_conf.client_bits = audio_bits;
tegra_set_cif(ope->regmap, reg, &cif_conf);
return 0;
}
static int tegra210_ope_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_ope *ope = snd_soc_dai_get_drvdata(dai);
int err;
/* Set RX and TX CIF */
err = tegra210_ope_set_audio_cif(ope, params,
TEGRA210_OPE_RX_CIF_CTRL);
if (err) {
dev_err(dev, "Can't set OPE RX CIF: %d\n", err);
return err;
}
err = tegra210_ope_set_audio_cif(ope, params,
TEGRA210_OPE_TX_CIF_CTRL);
if (err) {
dev_err(dev, "Can't set OPE TX CIF: %d\n", err);
return err;
}
tegra210_mbdrc_hw_params(dai->component);
return err;
}
static int tegra210_ope_component_probe(struct snd_soc_component *cmpnt)
{
struct tegra210_ope *ope = dev_get_drvdata(cmpnt->dev);
tegra210_peq_component_init(cmpnt);
tegra210_mbdrc_component_init(cmpnt);
/*
* The OPE, PEQ and MBDRC functionalities are combined under one
* device registered by OPE driver. In fact OPE HW block includes
* sub blocks PEQ and MBDRC. However driver registers separate
* regmap interfaces for each of these. ASoC core depends on
* dev_get_regmap() to populate the regmap field for a given ASoC
* component. A component can have one regmap reference and since
* the DAPM routes depend on OPE regmap only, below explicit
* assignment is done to highlight this. This is needed for ASoC
* core to access correct regmap during DAPM path setup.
*/
snd_soc_component_init_regmap(cmpnt, ope->regmap);
return 0;
}
static const struct snd_soc_dai_ops tegra210_ope_dai_ops = {
.hw_params = tegra210_ope_hw_params,
};
static struct snd_soc_dai_driver tegra210_ope_dais[] = {
{
.name = "OPE-RX-CIF",
.playback = {
.stream_name = "RX-CIF-Playback",
.channels_min = 1,
.channels_max = 8,
.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 = "RX-CIF-Capture",
.channels_min = 1,
.channels_max = 8,
.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,
},
},
{
.name = "OPE-TX-CIF",
.playback = {
.stream_name = "TX-CIF-Playback",
.channels_min = 1,
.channels_max = 8,
.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 = "TX-CIF-Capture",
.channels_min = 1,
.channels_max = 8,
.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,
},
.ops = &tegra210_ope_dai_ops,
}
};
static const struct snd_soc_dapm_widget tegra210_ope_widgets[] = {
SND_SOC_DAPM_AIF_IN("RX", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_OUT("TX", NULL, 0, TEGRA210_OPE_ENABLE,
TEGRA210_OPE_EN_SHIFT, 0),
};
#define OPE_ROUTES(sname) \
{ "RX XBAR-" sname, NULL, "XBAR-TX" }, \
{ "RX-CIF-" sname, NULL, "RX XBAR-" sname }, \
{ "RX", NULL, "RX-CIF-" sname }, \
{ "TX-CIF-" sname, NULL, "TX" }, \
{ "TX XBAR-" sname, NULL, "TX-CIF-" sname }, \
{ "XBAR-RX", NULL, "TX XBAR-" sname }
static const struct snd_soc_dapm_route tegra210_ope_routes[] = {
{ "TX", NULL, "RX" },
OPE_ROUTES("Playback"),
OPE_ROUTES("Capture"),
};
static const char * const tegra210_ope_data_dir_text[] = {
"MBDRC to PEQ",
"PEQ to MBDRC"
};
static const struct soc_enum tegra210_ope_data_dir_enum =
SOC_ENUM_SINGLE(TEGRA210_OPE_DIR, TEGRA210_OPE_DIR_SHIFT,
2, tegra210_ope_data_dir_text);
static int tegra210_ope_get_data_dir(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *cmpnt = snd_kcontrol_chip(kcontrol);
struct tegra210_ope *ope = snd_soc_component_get_drvdata(cmpnt);
ucontrol->value.enumerated.item[0] = ope->data_dir;
return 0;
}
static int tegra210_ope_put_data_dir(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *cmpnt = snd_kcontrol_chip(kcontrol);
struct tegra210_ope *ope = snd_soc_component_get_drvdata(cmpnt);
unsigned int value = ucontrol->value.enumerated.item[0];
if (value == ope->data_dir)
return 0;
ope->data_dir = value;
return 1;
}
static const struct snd_kcontrol_new tegra210_ope_controls[] = {
SOC_ENUM_EXT("Data Flow Direction", tegra210_ope_data_dir_enum,
tegra210_ope_get_data_dir, tegra210_ope_put_data_dir),
};
static const struct snd_soc_component_driver tegra210_ope_cmpnt = {
.probe = tegra210_ope_component_probe,
.dapm_widgets = tegra210_ope_widgets,
.num_dapm_widgets = ARRAY_SIZE(tegra210_ope_widgets),
.dapm_routes = tegra210_ope_routes,
.num_dapm_routes = ARRAY_SIZE(tegra210_ope_routes),
.controls = tegra210_ope_controls,
.num_controls = ARRAY_SIZE(tegra210_ope_controls),
};
static bool tegra210_ope_wr_reg(struct device *dev, unsigned int reg)
{
switch (reg) {
case TEGRA210_OPE_RX_INT_MASK ... TEGRA210_OPE_RX_CIF_CTRL:
case TEGRA210_OPE_TX_INT_MASK ... TEGRA210_OPE_TX_CIF_CTRL:
case TEGRA210_OPE_ENABLE ... TEGRA210_OPE_CG:
case TEGRA210_OPE_DIR:
return true;
default:
return false;
}
}
static bool tegra210_ope_rd_reg(struct device *dev, unsigned int reg)
{
if (tegra210_ope_wr_reg(dev, reg))
return true;
switch (reg) {
case TEGRA210_OPE_RX_STATUS:
case TEGRA210_OPE_RX_INT_STATUS:
case TEGRA210_OPE_TX_STATUS:
case TEGRA210_OPE_TX_INT_STATUS:
case TEGRA210_OPE_STATUS:
case TEGRA210_OPE_INT_STATUS:
return true;
default:
return false;
}
}
static bool tegra210_ope_volatile_reg(struct device *dev, unsigned int reg)
{
switch (reg) {
case TEGRA210_OPE_RX_STATUS:
case TEGRA210_OPE_RX_INT_STATUS:
case TEGRA210_OPE_TX_STATUS:
case TEGRA210_OPE_TX_INT_STATUS:
case TEGRA210_OPE_SOFT_RESET:
case TEGRA210_OPE_STATUS:
case TEGRA210_OPE_INT_STATUS:
return true;
default:
return false;
}
}
static const struct regmap_config tegra210_ope_regmap_config = {
.reg_bits = 32,
.reg_stride = 4,
.val_bits = 32,
.max_register = TEGRA210_OPE_DIR,
.writeable_reg = tegra210_ope_wr_reg,
.readable_reg = tegra210_ope_rd_reg,
.volatile_reg = tegra210_ope_volatile_reg,
.reg_defaults = tegra210_ope_reg_defaults,
.num_reg_defaults = ARRAY_SIZE(tegra210_ope_reg_defaults),
.reg_default_cb = regmap_default_zero_cb,
.cache_type = REGCACHE_FLAT,
};
static int tegra210_ope_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct tegra210_ope *ope;
void __iomem *regs;
int err;
ope = devm_kzalloc(dev, sizeof(*ope), GFP_KERNEL);
if (!ope)
return -ENOMEM;
regs = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(regs))
return PTR_ERR(regs);
ope->regmap = devm_regmap_init_mmio(dev, regs,
&tegra210_ope_regmap_config);
if (IS_ERR(ope->regmap))
return dev_err_probe(dev, PTR_ERR(ope->regmap),
"regmap init failed\n");
regcache_cache_only(ope->regmap, true);
dev_set_drvdata(dev, ope);
err = tegra210_peq_regmap_init(pdev);
if (err < 0)
return err;
err = tegra210_mbdrc_regmap_init(pdev);
if (err < 0)
return err;
err = devm_snd_soc_register_component(dev, &tegra210_ope_cmpnt,
tegra210_ope_dais,
ARRAY_SIZE(tegra210_ope_dais));
if (err)
return dev_err_probe(dev, err,
"can't register OPE component\n");
pm_runtime_enable(dev);
return 0;
}
static void tegra210_ope_remove(struct platform_device *pdev)
{
pm_runtime_disable(&pdev->dev);
}
static int tegra210_ope_runtime_suspend(struct device *dev)
{
struct tegra210_ope *ope = dev_get_drvdata(dev);
tegra210_peq_save(ope->peq_regmap, ope->peq_biquad_gains,
ope->peq_biquad_shifts);
regcache_cache_only(ope->mbdrc_regmap, true);
regcache_cache_only(ope->peq_regmap, true);
regcache_cache_only(ope->regmap, true);
regcache_mark_dirty(ope->regmap);
regcache_mark_dirty(ope->peq_regmap);
regcache_mark_dirty(ope->mbdrc_regmap);
return 0;
}
static int tegra210_ope_runtime_resume(struct device *dev)
{
struct tegra210_ope *ope = dev_get_drvdata(dev);
regcache_cache_only(ope->regmap, false);
regcache_cache_only(ope->peq_regmap, false);
regcache_cache_only(ope->mbdrc_regmap, false);
regcache_sync(ope->regmap);
regcache_sync(ope->peq_regmap);
regcache_sync(ope->mbdrc_regmap);
tegra210_peq_restore(ope->peq_regmap, ope->peq_biquad_gains,
ope->peq_biquad_shifts);
return 0;
}
static const struct dev_pm_ops tegra210_ope_pm_ops = {
RUNTIME_PM_OPS(tegra210_ope_runtime_suspend,
tegra210_ope_runtime_resume, NULL)
SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume)
};
static const struct of_device_id tegra210_ope_of_match[] = {
{ .compatible = "nvidia,tegra210-ope" },
{},
};
MODULE_DEVICE_TABLE(of, tegra210_ope_of_match);
static struct platform_driver tegra210_ope_driver = {
.driver = {
.name = "tegra210-ope",
.of_match_table = tegra210_ope_of_match,
.pm = pm_ptr(&tegra210_ope_pm_ops),
},
.probe = tegra210_ope_probe,
.remove = tegra210_ope_remove,
};
module_platform_driver(tegra210_ope_driver)
MODULE_AUTHOR("Sumit Bhattacharya <sumitb@nvidia.com>");
MODULE_DESCRIPTION("Tegra210 OPE ASoC driver");
MODULE_LICENSE("GPL");
@@ -0,0 +1,90 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* tegra210_ope.h - Definitions for Tegra210 OPE driver
*
* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved.
*
*/
#ifndef __TEGRA210_OPE_H__
#define __TEGRA210_OPE_H__
#include <linux/regmap.h>
#include <sound/soc.h>
#include "tegra210_peq.h"
/*
* OPE_RX registers are with respect to XBAR.
* The data comes from XBAR to OPE
*/
#define TEGRA210_OPE_RX_STATUS 0xc
#define TEGRA210_OPE_RX_INT_STATUS 0x10
#define TEGRA210_OPE_RX_INT_MASK 0x14
#define TEGRA210_OPE_RX_INT_SET 0x18
#define TEGRA210_OPE_RX_INT_CLEAR 0x1c
#define TEGRA210_OPE_RX_CIF_CTRL 0x20
/*
* OPE_TX registers are with respect to XBAR.
* The data goes out from OPE to XBAR
*/
#define TEGRA210_OPE_TX_STATUS 0x4c
#define TEGRA210_OPE_TX_INT_STATUS 0x50
#define TEGRA210_OPE_TX_INT_MASK 0x54
#define TEGRA210_OPE_TX_INT_SET 0x58
#define TEGRA210_OPE_TX_INT_CLEAR 0x5c
#define TEGRA210_OPE_TX_CIF_CTRL 0x60
/* OPE Gloabal registers */
#define TEGRA210_OPE_ENABLE 0x80
#define TEGRA210_OPE_SOFT_RESET 0x84
#define TEGRA210_OPE_CG 0x88
#define TEGRA210_OPE_STATUS 0x8c
#define TEGRA210_OPE_INT_STATUS 0x90
#define TEGRA210_OPE_DIR 0x94
/* Fields for TEGRA210_OPE_ENABLE */
#define TEGRA210_OPE_EN_SHIFT 0
#define TEGRA210_OPE_EN (1 << TEGRA210_OPE_EN_SHIFT)
/* Fields for TEGRA210_OPE_SOFT_RESET */
#define TEGRA210_OPE_SOFT_RESET_SHIFT 0
#define TEGRA210_OPE_SOFT_RESET_EN (1 << TEGRA210_OPE_SOFT_RESET_SHIFT)
#define TEGRA210_OPE_DIR_SHIFT 0
struct tegra210_ope {
struct regmap *regmap;
struct regmap *peq_regmap;
struct regmap *mbdrc_regmap;
u32 peq_biquad_gains[TEGRA210_PEQ_GAIN_PARAM_SIZE_PER_CH];
u32 peq_biquad_shifts[TEGRA210_PEQ_SHIFT_PARAM_SIZE_PER_CH];
unsigned int data_dir;
};
/* Extension of soc_bytes structure defined in sound/soc.h */
struct tegra_soc_bytes {
struct soc_bytes soc;
u32 shift; /* Used as offset for AHUB RAM related programing */
};
/* Utility structures for using mixer control of type snd_soc_bytes */
#define TEGRA_SOC_BYTES_EXT(xname, xbase, xregs, xshift, xmask, \
xhandler_get, xhandler_put, xinfo) \
{ \
.iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
.name = xname, \
.info = xinfo, \
.get = xhandler_get, \
.put = xhandler_put, \
.private_value = ((unsigned long)&(struct tegra_soc_bytes) \
{ \
.soc.base = xbase, \
.soc.num_regs = xregs, \
.soc.mask = xmask, \
.shift = xshift \
}) \
}
#endif
+435
View File
@@ -0,0 +1,435 @@
// SPDX-License-Identifier: GPL-2.0-only
//
// tegra210_peq.c - Tegra210 PEQ driver
//
// Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved.
#include <linux/clk.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include "tegra210_ope.h"
#include "tegra210_peq.h"
static const struct reg_default tegra210_peq_reg_defaults[] = {
{ TEGRA210_PEQ_CFG, 0x00000013},
{ TEGRA210_PEQ_CFG_RAM_CTRL, 0x00004000},
{ TEGRA210_PEQ_CFG_RAM_SHIFT_CTRL, 0x00004000},
};
static const u32 biquad_init_gains[TEGRA210_PEQ_GAIN_PARAM_SIZE_PER_CH] = {
1495012349, /* Pre-gain */
/* Gains : b0, b1, a0, a1, a2 */
536870912, -1073741824, 536870912, 2143508246, -1069773768, /* Band-0 */
134217728, -265414508, 131766272, 2140402222, -1071252997, /* Band-1 */
268435456, -233515765, -33935948, 1839817267, -773826124, /* Band-2 */
536870912, -672537913, 139851540, 1886437554, -824433167, /* Band-3 */
268435456, -114439279, 173723964, 205743566, 278809729, /* Band-4 */
1, 0, 0, 0, 0, /* Band-5 */
1, 0, 0, 0, 0, /* Band-6 */
1, 0, 0, 0, 0, /* Band-7 */
1, 0, 0, 0, 0, /* Band-8 */
1, 0, 0, 0, 0, /* Band-9 */
1, 0, 0, 0, 0, /* Band-10 */
1, 0, 0, 0, 0, /* Band-11 */
963423114, /* Post-gain */
};
static const u32 biquad_init_shifts[TEGRA210_PEQ_SHIFT_PARAM_SIZE_PER_CH] = {
23, /* Pre-shift */
30, 30, 30, 30, 30, 0, 0, 0, 0, 0, 0, 0, /* Shift for bands */
28, /* Post-shift */
};
static s32 biquad_coeff_buffer[TEGRA210_PEQ_GAIN_PARAM_SIZE_PER_CH];
static void tegra210_peq_read_ram(struct regmap *regmap, unsigned int reg_ctrl,
unsigned int reg_data, unsigned int ram_offset,
unsigned int *data, size_t size)
{
unsigned int val;
unsigned int i;
val = ram_offset & TEGRA210_PEQ_RAM_CTRL_RAM_ADDR_MASK;
val |= TEGRA210_PEQ_RAM_CTRL_ADDR_INIT_EN;
val |= TEGRA210_PEQ_RAM_CTRL_SEQ_ACCESS_EN;
val |= TEGRA210_PEQ_RAM_CTRL_RW_READ;
regmap_write(regmap, reg_ctrl, val);
/*
* Since all ahub non-io modules work under same ahub clock it is not
* necessary to check ahub read busy bit after every read.
*/
for (i = 0; i < size; i++)
regmap_read(regmap, reg_data, &data[i]);
}
static void tegra210_peq_write_ram(struct regmap *regmap, unsigned int reg_ctrl,
unsigned int reg_data, unsigned int ram_offset,
unsigned int *data, size_t size)
{
unsigned int val;
unsigned int i;
val = ram_offset & TEGRA210_PEQ_RAM_CTRL_RAM_ADDR_MASK;
val |= TEGRA210_PEQ_RAM_CTRL_ADDR_INIT_EN;
val |= TEGRA210_PEQ_RAM_CTRL_SEQ_ACCESS_EN;
val |= TEGRA210_PEQ_RAM_CTRL_RW_WRITE;
regmap_write(regmap, reg_ctrl, val);
for (i = 0; i < size; i++)
regmap_write(regmap, reg_data, data[i]);
}
static int tegra210_peq_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct soc_mixer_control *mc =
(struct soc_mixer_control *)kcontrol->private_value;
struct snd_soc_component *cmpnt = snd_kcontrol_chip(kcontrol);
struct tegra210_ope *ope = snd_soc_component_get_drvdata(cmpnt);
unsigned int mask = (1 << fls(mc->max)) - 1;
unsigned int val;
regmap_read(ope->peq_regmap, mc->reg, &val);
ucontrol->value.integer.value[0] = (val >> mc->shift) & mask;
if (!mc->invert)
return 0;
ucontrol->value.integer.value[0] =
mc->max - ucontrol->value.integer.value[0];
return 0;
}
static int tegra210_peq_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct soc_mixer_control *mc =
(struct soc_mixer_control *)kcontrol->private_value;
struct snd_soc_component *cmpnt = snd_kcontrol_chip(kcontrol);
struct tegra210_ope *ope = snd_soc_component_get_drvdata(cmpnt);
unsigned int mask = (1 << fls(mc->max)) - 1;
bool change = false;
unsigned int val;
val = (ucontrol->value.integer.value[0] & mask);
if (mc->invert)
val = mc->max - val;
val = val << mc->shift;
regmap_update_bits_check(ope->peq_regmap, mc->reg, (mask << mc->shift),
val, &change);
return change ? 1 : 0;
}
static int tegra210_peq_ram_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct tegra_soc_bytes *params = (void *)kcontrol->private_value;
struct snd_soc_component *cmpnt = snd_kcontrol_chip(kcontrol);
struct tegra210_ope *ope = snd_soc_component_get_drvdata(cmpnt);
int val_bytes = snd_soc_component_regmap_val_bytes(cmpnt);
u32 i, reg_ctrl = params->soc.base;
u32 reg_data = reg_ctrl + val_bytes;
s32 *data = (s32 *)biquad_coeff_buffer;
pm_runtime_get_sync(cmpnt->dev);
tegra210_peq_read_ram(ope->peq_regmap, reg_ctrl, reg_data,
params->shift, data, params->soc.num_regs);
pm_runtime_put_sync(cmpnt->dev);
for (i = 0; i < params->soc.num_regs; i++)
ucontrol->value.integer.value[i] = (long)data[i];
return 0;
}
static int tegra210_peq_ram_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct tegra_soc_bytes *params = (void *)kcontrol->private_value;
struct snd_soc_component *cmpnt = snd_kcontrol_chip(kcontrol);
struct tegra210_ope *ope = snd_soc_component_get_drvdata(cmpnt);
int val_bytes = snd_soc_component_regmap_val_bytes(cmpnt);
u32 i, reg_ctrl = params->soc.base;
u32 reg_data = reg_ctrl + val_bytes;
s32 *data = (s32 *)biquad_coeff_buffer;
for (i = 0; i < params->soc.num_regs; i++)
data[i] = (s32)ucontrol->value.integer.value[i];
pm_runtime_get_sync(cmpnt->dev);
tegra210_peq_write_ram(ope->peq_regmap, reg_ctrl, reg_data,
params->shift, data, params->soc.num_regs);
pm_runtime_put_sync(cmpnt->dev);
return 1;
}
static int tegra210_peq_param_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
struct soc_bytes *params = (void *)kcontrol->private_value;
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->value.integer.min = INT_MIN;
uinfo->value.integer.max = INT_MAX;
uinfo->count = params->num_regs;
return 0;
}
#define TEGRA210_PEQ_GAIN_PARAMS_CTRL(chan) \
TEGRA_SOC_BYTES_EXT("PEQ Channel-" #chan " Biquad Gain Params", \
TEGRA210_PEQ_CFG_RAM_CTRL, \
TEGRA210_PEQ_GAIN_PARAM_SIZE_PER_CH, \
(TEGRA210_PEQ_GAIN_PARAM_SIZE_PER_CH * chan), 0xffffffff, \
tegra210_peq_ram_get, tegra210_peq_ram_put, \
tegra210_peq_param_info)
#define TEGRA210_PEQ_SHIFT_PARAMS_CTRL(chan) \
TEGRA_SOC_BYTES_EXT("PEQ Channel-" #chan " Biquad Shift Params", \
TEGRA210_PEQ_CFG_RAM_SHIFT_CTRL, \
TEGRA210_PEQ_SHIFT_PARAM_SIZE_PER_CH, \
(TEGRA210_PEQ_SHIFT_PARAM_SIZE_PER_CH * chan), 0x1f, \
tegra210_peq_ram_get, tegra210_peq_ram_put, \
tegra210_peq_param_info)
static const struct snd_kcontrol_new tegra210_peq_controls[] = {
SOC_SINGLE_EXT("PEQ Active", TEGRA210_PEQ_CFG,
TEGRA210_PEQ_CFG_MODE_SHIFT, 1, 0,
tegra210_peq_get, tegra210_peq_put),
SOC_SINGLE_EXT("PEQ Biquad Stages", TEGRA210_PEQ_CFG,
TEGRA210_PEQ_CFG_BIQUAD_STAGES_SHIFT,
TEGRA210_PEQ_MAX_BIQUAD_STAGES - 1, 0,
tegra210_peq_get, tegra210_peq_put),
TEGRA210_PEQ_GAIN_PARAMS_CTRL(0),
TEGRA210_PEQ_GAIN_PARAMS_CTRL(1),
TEGRA210_PEQ_GAIN_PARAMS_CTRL(2),
TEGRA210_PEQ_GAIN_PARAMS_CTRL(3),
TEGRA210_PEQ_GAIN_PARAMS_CTRL(4),
TEGRA210_PEQ_GAIN_PARAMS_CTRL(5),
TEGRA210_PEQ_GAIN_PARAMS_CTRL(6),
TEGRA210_PEQ_GAIN_PARAMS_CTRL(7),
TEGRA210_PEQ_SHIFT_PARAMS_CTRL(0),
TEGRA210_PEQ_SHIFT_PARAMS_CTRL(1),
TEGRA210_PEQ_SHIFT_PARAMS_CTRL(2),
TEGRA210_PEQ_SHIFT_PARAMS_CTRL(3),
TEGRA210_PEQ_SHIFT_PARAMS_CTRL(4),
TEGRA210_PEQ_SHIFT_PARAMS_CTRL(5),
TEGRA210_PEQ_SHIFT_PARAMS_CTRL(6),
TEGRA210_PEQ_SHIFT_PARAMS_CTRL(7),
};
static bool tegra210_peq_wr_reg(struct device *dev, unsigned int reg)
{
switch (reg) {
case TEGRA210_PEQ_SOFT_RESET:
case TEGRA210_PEQ_CG:
case TEGRA210_PEQ_CFG ... TEGRA210_PEQ_CFG_RAM_SHIFT_DATA:
return true;
default:
return false;
}
}
static bool tegra210_peq_rd_reg(struct device *dev, unsigned int reg)
{
if (tegra210_peq_wr_reg(dev, reg))
return true;
switch (reg) {
case TEGRA210_PEQ_STATUS:
return true;
default:
return false;
}
}
static bool tegra210_peq_volatile_reg(struct device *dev, unsigned int reg)
{
switch (reg) {
case TEGRA210_PEQ_SOFT_RESET:
case TEGRA210_PEQ_STATUS:
case TEGRA210_PEQ_CFG_RAM_CTRL ... TEGRA210_PEQ_CFG_RAM_SHIFT_DATA:
return true;
default:
return false;
}
}
static bool tegra210_peq_precious_reg(struct device *dev, unsigned int reg)
{
switch (reg) {
case TEGRA210_PEQ_CFG_RAM_DATA:
case TEGRA210_PEQ_CFG_RAM_SHIFT_DATA:
return true;
default:
return false;
}
}
static const struct regmap_config tegra210_peq_regmap_config = {
.name = "peq",
.reg_bits = 32,
.reg_stride = 4,
.val_bits = 32,
.max_register = TEGRA210_PEQ_CFG_RAM_SHIFT_DATA,
.writeable_reg = tegra210_peq_wr_reg,
.readable_reg = tegra210_peq_rd_reg,
.volatile_reg = tegra210_peq_volatile_reg,
.precious_reg = tegra210_peq_precious_reg,
.reg_defaults = tegra210_peq_reg_defaults,
.num_reg_defaults = ARRAY_SIZE(tegra210_peq_reg_defaults),
.reg_default_cb = regmap_default_zero_cb,
.cache_type = REGCACHE_FLAT,
};
void tegra210_peq_restore(struct regmap *regmap, u32 *biquad_gains,
u32 *biquad_shifts)
{
unsigned int i;
for (i = 0; i < TEGRA210_PEQ_MAX_CHANNELS; i++) {
tegra210_peq_write_ram(regmap, TEGRA210_PEQ_CFG_RAM_CTRL,
TEGRA210_PEQ_CFG_RAM_DATA,
(i * TEGRA210_PEQ_GAIN_PARAM_SIZE_PER_CH),
biquad_gains,
TEGRA210_PEQ_GAIN_PARAM_SIZE_PER_CH);
tegra210_peq_write_ram(regmap,
TEGRA210_PEQ_CFG_RAM_SHIFT_CTRL,
TEGRA210_PEQ_CFG_RAM_SHIFT_DATA,
(i * TEGRA210_PEQ_SHIFT_PARAM_SIZE_PER_CH),
biquad_shifts,
TEGRA210_PEQ_SHIFT_PARAM_SIZE_PER_CH);
}
}
void tegra210_peq_save(struct regmap *regmap, u32 *biquad_gains,
u32 *biquad_shifts)
{
unsigned int i;
for (i = 0; i < TEGRA210_PEQ_MAX_CHANNELS; i++) {
tegra210_peq_read_ram(regmap,
TEGRA210_PEQ_CFG_RAM_CTRL,
TEGRA210_PEQ_CFG_RAM_DATA,
(i * TEGRA210_PEQ_GAIN_PARAM_SIZE_PER_CH),
biquad_gains,
TEGRA210_PEQ_GAIN_PARAM_SIZE_PER_CH);
tegra210_peq_read_ram(regmap,
TEGRA210_PEQ_CFG_RAM_SHIFT_CTRL,
TEGRA210_PEQ_CFG_RAM_SHIFT_DATA,
(i * TEGRA210_PEQ_SHIFT_PARAM_SIZE_PER_CH),
biquad_shifts,
TEGRA210_PEQ_SHIFT_PARAM_SIZE_PER_CH);
}
}
int tegra210_peq_component_init(struct snd_soc_component *cmpnt)
{
struct tegra210_ope *ope = snd_soc_component_get_drvdata(cmpnt);
unsigned int i;
pm_runtime_get_sync(cmpnt->dev);
regmap_update_bits(ope->peq_regmap, TEGRA210_PEQ_CFG,
TEGRA210_PEQ_CFG_MODE_MASK,
0 << TEGRA210_PEQ_CFG_MODE_SHIFT);
regmap_update_bits(ope->peq_regmap, TEGRA210_PEQ_CFG,
TEGRA210_PEQ_CFG_BIQUAD_STAGES_MASK,
(TEGRA210_PEQ_BIQUAD_INIT_STAGE - 1) <<
TEGRA210_PEQ_CFG_BIQUAD_STAGES_SHIFT);
/* Initialize PEQ AHUB RAM with default params */
for (i = 0; i < TEGRA210_PEQ_MAX_CHANNELS; i++) {
/* Set default gain params */
tegra210_peq_write_ram(ope->peq_regmap,
TEGRA210_PEQ_CFG_RAM_CTRL,
TEGRA210_PEQ_CFG_RAM_DATA,
(i * TEGRA210_PEQ_GAIN_PARAM_SIZE_PER_CH),
(u32 *)&biquad_init_gains,
TEGRA210_PEQ_GAIN_PARAM_SIZE_PER_CH);
/* Set default shift params */
tegra210_peq_write_ram(ope->peq_regmap,
TEGRA210_PEQ_CFG_RAM_SHIFT_CTRL,
TEGRA210_PEQ_CFG_RAM_SHIFT_DATA,
(i * TEGRA210_PEQ_SHIFT_PARAM_SIZE_PER_CH),
(u32 *)&biquad_init_shifts,
TEGRA210_PEQ_SHIFT_PARAM_SIZE_PER_CH);
}
pm_runtime_put_sync(cmpnt->dev);
snd_soc_add_component_controls(cmpnt, tegra210_peq_controls,
ARRAY_SIZE(tegra210_peq_controls));
return 0;
}
int tegra210_peq_regmap_init(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct tegra210_ope *ope = dev_get_drvdata(dev);
struct device_node *child;
struct resource mem;
void __iomem *regs;
int err;
child = of_get_child_by_name(dev->of_node, "equalizer");
if (!child)
return dev_err_probe(dev, -ENODEV,
"missing 'equalizer' DT child node\n");
err = of_address_to_resource(child, 0, &mem);
of_node_put(child);
if (err < 0)
return dev_err_probe(dev, err,
"failed to get PEQ resource\n");
mem.flags = IORESOURCE_MEM;
regs = devm_ioremap_resource(dev, &mem);
if (IS_ERR(regs))
return PTR_ERR(regs);
ope->peq_regmap = devm_regmap_init_mmio(dev, regs,
&tegra210_peq_regmap_config);
if (IS_ERR(ope->peq_regmap))
return dev_err_probe(dev, PTR_ERR(ope->peq_regmap),
"PEQ regmap init failed\n");
regcache_cache_only(ope->peq_regmap, true);
return 0;
}
@@ -0,0 +1,56 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* tegra210_peq.h - Definitions for Tegra210 PEQ driver
*
* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved.
*
*/
#ifndef __TEGRA210_PEQ_H__
#define __TEGRA210_PEQ_H__
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <sound/soc.h>
/* Register offsets from PEQ base */
#define TEGRA210_PEQ_SOFT_RESET 0x0
#define TEGRA210_PEQ_CG 0x4
#define TEGRA210_PEQ_STATUS 0x8
#define TEGRA210_PEQ_CFG 0xc
#define TEGRA210_PEQ_CFG_RAM_CTRL 0x10
#define TEGRA210_PEQ_CFG_RAM_DATA 0x14
#define TEGRA210_PEQ_CFG_RAM_SHIFT_CTRL 0x18
#define TEGRA210_PEQ_CFG_RAM_SHIFT_DATA 0x1c
/* Fields in TEGRA210_PEQ_CFG */
#define TEGRA210_PEQ_CFG_BIQUAD_STAGES_SHIFT 2
#define TEGRA210_PEQ_CFG_BIQUAD_STAGES_MASK (0xf << TEGRA210_PEQ_CFG_BIQUAD_STAGES_SHIFT)
#define TEGRA210_PEQ_CFG_MODE_SHIFT 0
#define TEGRA210_PEQ_CFG_MODE_MASK (0x1 << TEGRA210_PEQ_CFG_MODE_SHIFT)
#define TEGRA210_PEQ_RAM_CTRL_RW_READ 0
#define TEGRA210_PEQ_RAM_CTRL_RW_WRITE (1 << 14)
#define TEGRA210_PEQ_RAM_CTRL_ADDR_INIT_EN (1 << 13)
#define TEGRA210_PEQ_RAM_CTRL_SEQ_ACCESS_EN (1 << 12)
#define TEGRA210_PEQ_RAM_CTRL_RAM_ADDR_MASK 0x1ff
/* PEQ register definition ends here */
#define TEGRA210_PEQ_MAX_BIQUAD_STAGES 12
#define TEGRA210_PEQ_MAX_CHANNELS 8
#define TEGRA210_PEQ_BIQUAD_INIT_STAGE 5
#define TEGRA210_PEQ_GAIN_PARAM_SIZE_PER_CH (2 + TEGRA210_PEQ_MAX_BIQUAD_STAGES * 5)
#define TEGRA210_PEQ_SHIFT_PARAM_SIZE_PER_CH (2 + TEGRA210_PEQ_MAX_BIQUAD_STAGES)
int tegra210_peq_regmap_init(struct platform_device *pdev);
int tegra210_peq_component_init(struct snd_soc_component *cmpnt);
void tegra210_peq_restore(struct regmap *regmap, u32 *biquad_gains,
u32 *biquad_shifts);
void tegra210_peq_save(struct regmap *regmap, u32 *biquad_gains,
u32 *biquad_shifts);
#endif
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,78 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* tegra210_sfc.h - Definitions for Tegra210 SFC driver
*
* Copyright (c) 2021-2023 NVIDIA CORPORATION. All rights reserved.
*
*/
#ifndef __TEGRA210_SFC_H__
#define __TEGRA210_SFC_H__
/*
* SFC_RX registers are with respect to XBAR.
* The data comes from XBAR to SFC.
*/
#define TEGRA210_SFC_RX_STATUS 0x0c
#define TEGRA210_SFC_RX_INT_STATUS 0x10
#define TEGRA210_SFC_RX_INT_MASK 0x14
#define TEGRA210_SFC_RX_INT_SET 0x18
#define TEGRA210_SFC_RX_INT_CLEAR 0x1c
#define TEGRA210_SFC_RX_CIF_CTRL 0x20
#define TEGRA210_SFC_RX_FREQ 0x24
/*
* SFC_TX registers are with respect to XBAR.
* The data goes out of SFC.
*/
#define TEGRA210_SFC_TX_STATUS 0x4c
#define TEGRA210_SFC_TX_INT_STATUS 0x50
#define TEGRA210_SFC_TX_INT_MASK 0x54
#define TEGRA210_SFC_TX_INT_SET 0x58
#define TEGRA210_SFC_TX_INT_CLEAR 0x5c
#define TEGRA210_SFC_TX_CIF_CTRL 0x60
#define TEGRA210_SFC_TX_FREQ 0x64
/* Register offsets from TEGRA210_SFC*_BASE */
#define TEGRA210_SFC_ENABLE 0x80
#define TEGRA210_SFC_SOFT_RESET 0x84
#define TEGRA210_SFC_CG 0x88
#define TEGRA210_SFC_STATUS 0x8c
#define TEGRA210_SFC_INT_STATUS 0x90
#define TEGRA210_SFC_COEF_RAM 0xbc
#define TEGRA210_SFC_CFG_RAM_CTRL 0xc0
#define TEGRA210_SFC_CFG_RAM_DATA 0xc4
/* Fields in TEGRA210_SFC_ENABLE */
#define TEGRA210_SFC_EN_SHIFT 0
#define TEGRA210_SFC_EN (1 << TEGRA210_SFC_EN_SHIFT)
#define TEGRA210_SFC_NUM_RATES 13
/* Fields in TEGRA210_SFC_COEF_RAM */
#define TEGRA210_SFC_COEF_RAM_EN BIT(0)
#define TEGRA210_SFC_SOFT_RESET_EN BIT(0)
/* Coefficients */
#define TEGRA210_SFC_COEF_RAM_DEPTH 64
#define TEGRA210_SFC_RAM_CTRL_RW_WRITE (1 << 14)
#define TEGRA210_SFC_RAM_CTRL_ADDR_INIT_EN (1 << 13)
#define TEGRA210_SFC_RAM_CTRL_SEQ_ACCESS_EN (1 << 12)
enum tegra210_sfc_path {
SFC_RX_PATH,
SFC_TX_PATH,
SFC_PATHS,
};
struct tegra210_sfc {
unsigned int mono_to_stereo[SFC_PATHS];
unsigned int stereo_to_mono[SFC_PATHS];
unsigned int srate_out;
unsigned int srate_in;
struct regmap *regmap;
};
#endif
+687
View File
@@ -0,0 +1,687 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* tegra30_ahub.c - Tegra30 AHUB driver
*
* Copyright (c) 2011,2012, NVIDIA CORPORATION. All rights reserved.
*/
#include <linux/clk.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include <linux/reset.h>
#include <linux/slab.h>
#include <sound/soc.h>
#include "tegra30_ahub.h"
#define DRV_NAME "tegra30-ahub"
static struct tegra30_ahub *ahub;
static inline void tegra30_apbif_write(u32 reg, u32 val)
{
regmap_write(ahub->regmap_apbif, reg, val);
}
static inline u32 tegra30_apbif_read(u32 reg)
{
u32 val;
regmap_read(ahub->regmap_apbif, reg, &val);
return val;
}
static inline void tegra30_audio_write(u32 reg, u32 val)
{
regmap_write(ahub->regmap_ahub, reg, val);
}
static int tegra30_ahub_runtime_suspend(struct device *dev)
{
regcache_cache_only(ahub->regmap_apbif, true);
regcache_cache_only(ahub->regmap_ahub, true);
clk_bulk_disable_unprepare(ahub->nclocks, ahub->clocks);
return 0;
}
/*
* clk_apbif isn't required for an I2S<->I2S configuration where no PCM data
* is read from or sent to memory. However, that's not something the rest of
* the driver supports right now, so we'll just treat the two clocks as one
* for now.
*
* These functions should not be a plain ref-count. Instead, each active stream
* contributes some requirement to the minimum clock rate, so starting or
* stopping streams should dynamically adjust the clock as required. However,
* this is not yet implemented.
*/
static int tegra30_ahub_runtime_resume(struct device *dev)
{
int ret;
ret = reset_control_bulk_assert(ahub->nresets, ahub->resets);
if (ret)
return ret;
ret = clk_bulk_prepare_enable(ahub->nclocks, ahub->clocks);
if (ret)
return ret;
usleep_range(10, 100);
ret = reset_control_bulk_deassert(ahub->nresets, ahub->resets);
if (ret)
goto disable_clocks;
regcache_cache_only(ahub->regmap_apbif, false);
regcache_cache_only(ahub->regmap_ahub, false);
regcache_mark_dirty(ahub->regmap_apbif);
regcache_mark_dirty(ahub->regmap_ahub);
ret = regcache_sync(ahub->regmap_apbif);
if (ret)
goto disable_clocks;
ret = regcache_sync(ahub->regmap_ahub);
if (ret)
goto disable_clocks;
return 0;
disable_clocks:
clk_bulk_disable_unprepare(ahub->nclocks, ahub->clocks);
return ret;
}
int tegra30_ahub_allocate_rx_fifo(enum tegra30_ahub_rxcif *rxcif,
char *dmachan, int dmachan_len,
dma_addr_t *fiforeg)
{
int channel;
u32 reg, val;
struct tegra30_ahub_cif_conf cif_conf;
channel = find_first_zero_bit(ahub->rx_usage,
TEGRA30_AHUB_CHANNEL_CTRL_COUNT);
if (channel >= TEGRA30_AHUB_CHANNEL_CTRL_COUNT)
return -EBUSY;
__set_bit(channel, ahub->rx_usage);
*rxcif = TEGRA30_AHUB_RXCIF_APBIF_RX0 + channel;
snprintf(dmachan, dmachan_len, "rx%d", channel);
*fiforeg = ahub->apbif_addr + TEGRA30_AHUB_CHANNEL_RXFIFO +
(channel * TEGRA30_AHUB_CHANNEL_RXFIFO_STRIDE);
pm_runtime_get_sync(ahub->dev);
reg = TEGRA30_AHUB_CHANNEL_CTRL +
(channel * TEGRA30_AHUB_CHANNEL_CTRL_STRIDE);
val = tegra30_apbif_read(reg);
val &= ~(TEGRA30_AHUB_CHANNEL_CTRL_RX_THRESHOLD_MASK |
TEGRA30_AHUB_CHANNEL_CTRL_RX_PACK_MASK);
val |= (7 << TEGRA30_AHUB_CHANNEL_CTRL_RX_THRESHOLD_SHIFT) |
TEGRA30_AHUB_CHANNEL_CTRL_RX_PACK_EN |
TEGRA30_AHUB_CHANNEL_CTRL_RX_PACK_16;
tegra30_apbif_write(reg, val);
cif_conf.threshold = 0;
cif_conf.audio_channels = 2;
cif_conf.client_channels = 2;
cif_conf.audio_bits = TEGRA30_AUDIOCIF_BITS_16;
cif_conf.client_bits = TEGRA30_AUDIOCIF_BITS_16;
cif_conf.expand = 0;
cif_conf.stereo_conv = 0;
cif_conf.replicate = 0;
cif_conf.direction = TEGRA30_AUDIOCIF_DIRECTION_RX;
cif_conf.truncate = 0;
cif_conf.mono_conv = 0;
reg = TEGRA30_AHUB_CIF_RX_CTRL +
(channel * TEGRA30_AHUB_CIF_RX_CTRL_STRIDE);
ahub->soc_data->set_audio_cif(ahub->regmap_apbif, reg, &cif_conf);
pm_runtime_put(ahub->dev);
return 0;
}
EXPORT_SYMBOL_GPL(tegra30_ahub_allocate_rx_fifo);
int tegra30_ahub_enable_rx_fifo(enum tegra30_ahub_rxcif rxcif)
{
int channel = rxcif - TEGRA30_AHUB_RXCIF_APBIF_RX0;
int reg, val;
pm_runtime_get_sync(ahub->dev);
reg = TEGRA30_AHUB_CHANNEL_CTRL +
(channel * TEGRA30_AHUB_CHANNEL_CTRL_STRIDE);
val = tegra30_apbif_read(reg);
val |= TEGRA30_AHUB_CHANNEL_CTRL_RX_EN;
tegra30_apbif_write(reg, val);
pm_runtime_put(ahub->dev);
return 0;
}
EXPORT_SYMBOL_GPL(tegra30_ahub_enable_rx_fifo);
int tegra30_ahub_disable_rx_fifo(enum tegra30_ahub_rxcif rxcif)
{
int channel = rxcif - TEGRA30_AHUB_RXCIF_APBIF_RX0;
int reg, val;
pm_runtime_get_sync(ahub->dev);
reg = TEGRA30_AHUB_CHANNEL_CTRL +
(channel * TEGRA30_AHUB_CHANNEL_CTRL_STRIDE);
val = tegra30_apbif_read(reg);
val &= ~TEGRA30_AHUB_CHANNEL_CTRL_RX_EN;
tegra30_apbif_write(reg, val);
pm_runtime_put(ahub->dev);
return 0;
}
EXPORT_SYMBOL_GPL(tegra30_ahub_disable_rx_fifo);
int tegra30_ahub_free_rx_fifo(enum tegra30_ahub_rxcif rxcif)
{
int channel = rxcif - TEGRA30_AHUB_RXCIF_APBIF_RX0;
__clear_bit(channel, ahub->rx_usage);
return 0;
}
EXPORT_SYMBOL_GPL(tegra30_ahub_free_rx_fifo);
int tegra30_ahub_allocate_tx_fifo(enum tegra30_ahub_txcif *txcif,
char *dmachan, int dmachan_len,
dma_addr_t *fiforeg)
{
int channel;
u32 reg, val;
struct tegra30_ahub_cif_conf cif_conf;
channel = find_first_zero_bit(ahub->tx_usage,
TEGRA30_AHUB_CHANNEL_CTRL_COUNT);
if (channel >= TEGRA30_AHUB_CHANNEL_CTRL_COUNT)
return -EBUSY;
__set_bit(channel, ahub->tx_usage);
*txcif = TEGRA30_AHUB_TXCIF_APBIF_TX0 + channel;
snprintf(dmachan, dmachan_len, "tx%d", channel);
*fiforeg = ahub->apbif_addr + TEGRA30_AHUB_CHANNEL_TXFIFO +
(channel * TEGRA30_AHUB_CHANNEL_TXFIFO_STRIDE);
pm_runtime_get_sync(ahub->dev);
reg = TEGRA30_AHUB_CHANNEL_CTRL +
(channel * TEGRA30_AHUB_CHANNEL_CTRL_STRIDE);
val = tegra30_apbif_read(reg);
val &= ~(TEGRA30_AHUB_CHANNEL_CTRL_TX_THRESHOLD_MASK |
TEGRA30_AHUB_CHANNEL_CTRL_TX_PACK_MASK);
val |= (7 << TEGRA30_AHUB_CHANNEL_CTRL_TX_THRESHOLD_SHIFT) |
TEGRA30_AHUB_CHANNEL_CTRL_TX_PACK_EN |
TEGRA30_AHUB_CHANNEL_CTRL_TX_PACK_16;
tegra30_apbif_write(reg, val);
cif_conf.threshold = 0;
cif_conf.audio_channels = 2;
cif_conf.client_channels = 2;
cif_conf.audio_bits = TEGRA30_AUDIOCIF_BITS_16;
cif_conf.client_bits = TEGRA30_AUDIOCIF_BITS_16;
cif_conf.expand = 0;
cif_conf.stereo_conv = 0;
cif_conf.replicate = 0;
cif_conf.direction = TEGRA30_AUDIOCIF_DIRECTION_TX;
cif_conf.truncate = 0;
cif_conf.mono_conv = 0;
reg = TEGRA30_AHUB_CIF_TX_CTRL +
(channel * TEGRA30_AHUB_CIF_TX_CTRL_STRIDE);
ahub->soc_data->set_audio_cif(ahub->regmap_apbif, reg, &cif_conf);
pm_runtime_put(ahub->dev);
return 0;
}
EXPORT_SYMBOL_GPL(tegra30_ahub_allocate_tx_fifo);
int tegra30_ahub_enable_tx_fifo(enum tegra30_ahub_txcif txcif)
{
int channel = txcif - TEGRA30_AHUB_TXCIF_APBIF_TX0;
int reg, val;
pm_runtime_get_sync(ahub->dev);
reg = TEGRA30_AHUB_CHANNEL_CTRL +
(channel * TEGRA30_AHUB_CHANNEL_CTRL_STRIDE);
val = tegra30_apbif_read(reg);
val |= TEGRA30_AHUB_CHANNEL_CTRL_TX_EN;
tegra30_apbif_write(reg, val);
pm_runtime_put(ahub->dev);
return 0;
}
EXPORT_SYMBOL_GPL(tegra30_ahub_enable_tx_fifo);
int tegra30_ahub_disable_tx_fifo(enum tegra30_ahub_txcif txcif)
{
int channel = txcif - TEGRA30_AHUB_TXCIF_APBIF_TX0;
int reg, val;
pm_runtime_get_sync(ahub->dev);
reg = TEGRA30_AHUB_CHANNEL_CTRL +
(channel * TEGRA30_AHUB_CHANNEL_CTRL_STRIDE);
val = tegra30_apbif_read(reg);
val &= ~TEGRA30_AHUB_CHANNEL_CTRL_TX_EN;
tegra30_apbif_write(reg, val);
pm_runtime_put(ahub->dev);
return 0;
}
EXPORT_SYMBOL_GPL(tegra30_ahub_disable_tx_fifo);
int tegra30_ahub_free_tx_fifo(enum tegra30_ahub_txcif txcif)
{
int channel = txcif - TEGRA30_AHUB_TXCIF_APBIF_TX0;
__clear_bit(channel, ahub->tx_usage);
return 0;
}
EXPORT_SYMBOL_GPL(tegra30_ahub_free_tx_fifo);
int tegra30_ahub_set_rx_cif_source(enum tegra30_ahub_rxcif rxcif,
enum tegra30_ahub_txcif txcif)
{
int channel = rxcif - TEGRA30_AHUB_RXCIF_APBIF_RX0;
int reg;
pm_runtime_get_sync(ahub->dev);
reg = TEGRA30_AHUB_AUDIO_RX +
(channel * TEGRA30_AHUB_AUDIO_RX_STRIDE);
tegra30_audio_write(reg, 1 << txcif);
pm_runtime_put(ahub->dev);
return 0;
}
EXPORT_SYMBOL_GPL(tegra30_ahub_set_rx_cif_source);
int tegra30_ahub_unset_rx_cif_source(enum tegra30_ahub_rxcif rxcif)
{
int channel = rxcif - TEGRA30_AHUB_RXCIF_APBIF_RX0;
int reg;
pm_runtime_get_sync(ahub->dev);
reg = TEGRA30_AHUB_AUDIO_RX +
(channel * TEGRA30_AHUB_AUDIO_RX_STRIDE);
tegra30_audio_write(reg, 0);
pm_runtime_put(ahub->dev);
return 0;
}
EXPORT_SYMBOL_GPL(tegra30_ahub_unset_rx_cif_source);
static const struct reset_control_bulk_data tegra30_ahub_resets_data[] = {
{ "d_audio" },
{ "apbif" },
{ "i2s0" },
{ "i2s1" },
{ "i2s2" },
{ "i2s3" },
{ "i2s4" },
{ "dam0" },
{ "dam1" },
{ "dam2" },
{ "spdif" },
{ "amx" }, /* Tegra114+ */
{ "adx" }, /* Tegra114+ */
{ "amx1" }, /* Tegra124 */
{ "adx1" }, /* Tegra124 */
{ "afc0" }, /* Tegra124 */
{ "afc1" }, /* Tegra124 */
{ "afc2" }, /* Tegra124 */
{ "afc3" }, /* Tegra124 */
{ "afc4" }, /* Tegra124 */
{ "afc5" }, /* Tegra124 */
};
#define LAST_REG(name) \
(TEGRA30_AHUB_##name + \
(TEGRA30_AHUB_##name##_STRIDE * TEGRA30_AHUB_##name##_COUNT) - 4)
#define REG_IN_ARRAY(reg, name) \
((reg >= TEGRA30_AHUB_##name) && \
(reg <= LAST_REG(name) && \
(!((reg - TEGRA30_AHUB_##name) % TEGRA30_AHUB_##name##_STRIDE))))
static bool tegra30_ahub_apbif_wr_rd_reg(struct device *dev, unsigned int reg)
{
switch (reg) {
case TEGRA30_AHUB_CONFIG_LINK_CTRL:
case TEGRA30_AHUB_MISC_CTRL:
case TEGRA30_AHUB_APBDMA_LIVE_STATUS:
case TEGRA30_AHUB_I2S_LIVE_STATUS:
case TEGRA30_AHUB_SPDIF_LIVE_STATUS:
case TEGRA30_AHUB_I2S_INT_MASK:
case TEGRA30_AHUB_DAM_INT_MASK:
case TEGRA30_AHUB_SPDIF_INT_MASK:
case TEGRA30_AHUB_APBIF_INT_MASK:
case TEGRA30_AHUB_I2S_INT_STATUS:
case TEGRA30_AHUB_DAM_INT_STATUS:
case TEGRA30_AHUB_SPDIF_INT_STATUS:
case TEGRA30_AHUB_APBIF_INT_STATUS:
case TEGRA30_AHUB_I2S_INT_SOURCE:
case TEGRA30_AHUB_DAM_INT_SOURCE:
case TEGRA30_AHUB_SPDIF_INT_SOURCE:
case TEGRA30_AHUB_APBIF_INT_SOURCE:
case TEGRA30_AHUB_I2S_INT_SET:
case TEGRA30_AHUB_DAM_INT_SET:
case TEGRA30_AHUB_SPDIF_INT_SET:
case TEGRA30_AHUB_APBIF_INT_SET:
return true;
default:
break;
}
if (REG_IN_ARRAY(reg, CHANNEL_CTRL) ||
REG_IN_ARRAY(reg, CHANNEL_CLEAR) ||
REG_IN_ARRAY(reg, CHANNEL_STATUS) ||
REG_IN_ARRAY(reg, CHANNEL_TXFIFO) ||
REG_IN_ARRAY(reg, CHANNEL_RXFIFO) ||
REG_IN_ARRAY(reg, CIF_TX_CTRL) ||
REG_IN_ARRAY(reg, CIF_RX_CTRL) ||
REG_IN_ARRAY(reg, DAM_LIVE_STATUS))
return true;
return false;
}
static bool tegra30_ahub_apbif_volatile_reg(struct device *dev,
unsigned int reg)
{
switch (reg) {
case TEGRA30_AHUB_CONFIG_LINK_CTRL:
case TEGRA30_AHUB_MISC_CTRL:
case TEGRA30_AHUB_APBDMA_LIVE_STATUS:
case TEGRA30_AHUB_I2S_LIVE_STATUS:
case TEGRA30_AHUB_SPDIF_LIVE_STATUS:
case TEGRA30_AHUB_I2S_INT_STATUS:
case TEGRA30_AHUB_DAM_INT_STATUS:
case TEGRA30_AHUB_SPDIF_INT_STATUS:
case TEGRA30_AHUB_APBIF_INT_STATUS:
case TEGRA30_AHUB_I2S_INT_SET:
case TEGRA30_AHUB_DAM_INT_SET:
case TEGRA30_AHUB_SPDIF_INT_SET:
case TEGRA30_AHUB_APBIF_INT_SET:
return true;
default:
break;
}
if (REG_IN_ARRAY(reg, CHANNEL_CLEAR) ||
REG_IN_ARRAY(reg, CHANNEL_STATUS) ||
REG_IN_ARRAY(reg, CHANNEL_TXFIFO) ||
REG_IN_ARRAY(reg, CHANNEL_RXFIFO) ||
REG_IN_ARRAY(reg, DAM_LIVE_STATUS))
return true;
return false;
}
static bool tegra30_ahub_apbif_precious_reg(struct device *dev,
unsigned int reg)
{
if (REG_IN_ARRAY(reg, CHANNEL_TXFIFO) ||
REG_IN_ARRAY(reg, CHANNEL_RXFIFO))
return true;
return false;
}
static const struct regmap_config tegra30_ahub_apbif_regmap_config = {
.name = "apbif",
.reg_bits = 32,
.val_bits = 32,
.reg_stride = 4,
.max_register = TEGRA30_AHUB_APBIF_INT_SET,
.writeable_reg = tegra30_ahub_apbif_wr_rd_reg,
.readable_reg = tegra30_ahub_apbif_wr_rd_reg,
.volatile_reg = tegra30_ahub_apbif_volatile_reg,
.precious_reg = tegra30_ahub_apbif_precious_reg,
.cache_type = REGCACHE_FLAT,
};
static bool tegra30_ahub_ahub_wr_rd_reg(struct device *dev, unsigned int reg)
{
if (REG_IN_ARRAY(reg, AUDIO_RX))
return true;
return false;
}
static const struct regmap_config tegra30_ahub_ahub_regmap_config = {
.name = "ahub",
.reg_bits = 32,
.val_bits = 32,
.reg_stride = 4,
.max_register = LAST_REG(AUDIO_RX),
.writeable_reg = tegra30_ahub_ahub_wr_rd_reg,
.readable_reg = tegra30_ahub_ahub_wr_rd_reg,
.cache_type = REGCACHE_FLAT,
};
static struct tegra30_ahub_soc_data soc_data_tegra30 = {
.num_resets = 11,
.set_audio_cif = tegra30_ahub_set_cif,
};
static struct tegra30_ahub_soc_data soc_data_tegra114 = {
.num_resets = 13,
.set_audio_cif = tegra30_ahub_set_cif,
};
static struct tegra30_ahub_soc_data soc_data_tegra124 = {
.num_resets = 21,
.set_audio_cif = tegra124_ahub_set_cif,
};
static const struct of_device_id tegra30_ahub_of_match[] = {
{ .compatible = "nvidia,tegra124-ahub", .data = &soc_data_tegra124 },
{ .compatible = "nvidia,tegra114-ahub", .data = &soc_data_tegra114 },
{ .compatible = "nvidia,tegra30-ahub", .data = &soc_data_tegra30 },
{},
};
MODULE_DEVICE_TABLE(of, tegra30_ahub_of_match);
static int tegra30_ahub_probe(struct platform_device *pdev)
{
const struct tegra30_ahub_soc_data *soc_data;
struct resource *res0;
void __iomem *regs_apbif, *regs_ahub;
int ret = 0;
soc_data = of_device_get_match_data(&pdev->dev);
if (!soc_data)
return -EINVAL;
ahub = devm_kzalloc(&pdev->dev, sizeof(struct tegra30_ahub),
GFP_KERNEL);
if (!ahub)
return -ENOMEM;
dev_set_drvdata(&pdev->dev, ahub);
BUILD_BUG_ON(sizeof(ahub->resets) != sizeof(tegra30_ahub_resets_data));
memcpy(ahub->resets, tegra30_ahub_resets_data, sizeof(ahub->resets));
ahub->nresets = soc_data->num_resets;
ahub->soc_data = soc_data;
ahub->dev = &pdev->dev;
ahub->clocks[ahub->nclocks++].id = "apbif";
ahub->clocks[ahub->nclocks++].id = "d_audio";
ret = devm_clk_bulk_get(&pdev->dev, ahub->nclocks, ahub->clocks);
if (ret)
goto err_unset_ahub;
ret = devm_reset_control_bulk_get_exclusive(&pdev->dev, ahub->nresets,
ahub->resets);
if (ret) {
dev_err(&pdev->dev, "Can't get resets: %d\n", ret);
goto err_unset_ahub;
}
regs_apbif = devm_platform_get_and_ioremap_resource(pdev, 0, &res0);
if (IS_ERR(regs_apbif)) {
ret = PTR_ERR(regs_apbif);
goto err_unset_ahub;
}
ahub->apbif_addr = res0->start;
ahub->regmap_apbif = devm_regmap_init_mmio(&pdev->dev, regs_apbif,
&tegra30_ahub_apbif_regmap_config);
if (IS_ERR(ahub->regmap_apbif)) {
dev_err(&pdev->dev, "apbif regmap init failed\n");
ret = PTR_ERR(ahub->regmap_apbif);
goto err_unset_ahub;
}
regcache_cache_only(ahub->regmap_apbif, true);
regs_ahub = devm_platform_ioremap_resource(pdev, 1);
if (IS_ERR(regs_ahub)) {
ret = PTR_ERR(regs_ahub);
goto err_unset_ahub;
}
ahub->regmap_ahub = devm_regmap_init_mmio(&pdev->dev, regs_ahub,
&tegra30_ahub_ahub_regmap_config);
if (IS_ERR(ahub->regmap_ahub)) {
dev_err(&pdev->dev, "ahub regmap init failed\n");
ret = PTR_ERR(ahub->regmap_ahub);
goto err_unset_ahub;
}
regcache_cache_only(ahub->regmap_ahub, true);
pm_runtime_enable(&pdev->dev);
of_platform_populate(pdev->dev.of_node, NULL, NULL, &pdev->dev);
return 0;
err_unset_ahub:
ahub = NULL;
return ret;
}
static void tegra30_ahub_remove(struct platform_device *pdev)
{
pm_runtime_disable(&pdev->dev);
ahub = NULL;
}
static const struct dev_pm_ops tegra30_ahub_pm_ops = {
RUNTIME_PM_OPS(tegra30_ahub_runtime_suspend,
tegra30_ahub_runtime_resume, NULL)
SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume)
};
static struct platform_driver tegra30_ahub_driver = {
.probe = tegra30_ahub_probe,
.remove = tegra30_ahub_remove,
.driver = {
.name = DRV_NAME,
.of_match_table = tegra30_ahub_of_match,
.pm = pm_ptr(&tegra30_ahub_pm_ops),
},
};
module_platform_driver(tegra30_ahub_driver);
void tegra30_ahub_set_cif(struct regmap *regmap, unsigned int reg,
struct tegra30_ahub_cif_conf *conf)
{
unsigned int value;
value = (conf->threshold <<
TEGRA30_AUDIOCIF_CTRL_FIFO_THRESHOLD_SHIFT) |
((conf->audio_channels - 1) <<
TEGRA30_AUDIOCIF_CTRL_AUDIO_CHANNELS_SHIFT) |
((conf->client_channels - 1) <<
TEGRA30_AUDIOCIF_CTRL_CLIENT_CHANNELS_SHIFT) |
(conf->audio_bits <<
TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_SHIFT) |
(conf->client_bits <<
TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_SHIFT) |
(conf->expand <<
TEGRA30_AUDIOCIF_CTRL_EXPAND_SHIFT) |
(conf->stereo_conv <<
TEGRA30_AUDIOCIF_CTRL_STEREO_CONV_SHIFT) |
(conf->replicate <<
TEGRA30_AUDIOCIF_CTRL_REPLICATE_SHIFT) |
(conf->direction <<
TEGRA30_AUDIOCIF_CTRL_DIRECTION_SHIFT) |
(conf->truncate <<
TEGRA30_AUDIOCIF_CTRL_TRUNCATE_SHIFT) |
(conf->mono_conv <<
TEGRA30_AUDIOCIF_CTRL_MONO_CONV_SHIFT);
regmap_write(regmap, reg, value);
}
EXPORT_SYMBOL_GPL(tegra30_ahub_set_cif);
void tegra124_ahub_set_cif(struct regmap *regmap, unsigned int reg,
struct tegra30_ahub_cif_conf *conf)
{
unsigned int value;
value = (conf->threshold <<
TEGRA124_AUDIOCIF_CTRL_FIFO_THRESHOLD_SHIFT) |
((conf->audio_channels - 1) <<
TEGRA124_AUDIOCIF_CTRL_AUDIO_CHANNELS_SHIFT) |
((conf->client_channels - 1) <<
TEGRA124_AUDIOCIF_CTRL_CLIENT_CHANNELS_SHIFT) |
(conf->audio_bits <<
TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_SHIFT) |
(conf->client_bits <<
TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_SHIFT) |
(conf->expand <<
TEGRA30_AUDIOCIF_CTRL_EXPAND_SHIFT) |
(conf->stereo_conv <<
TEGRA30_AUDIOCIF_CTRL_STEREO_CONV_SHIFT) |
(conf->replicate <<
TEGRA30_AUDIOCIF_CTRL_REPLICATE_SHIFT) |
(conf->direction <<
TEGRA30_AUDIOCIF_CTRL_DIRECTION_SHIFT) |
(conf->truncate <<
TEGRA30_AUDIOCIF_CTRL_TRUNCATE_SHIFT) |
(conf->mono_conv <<
TEGRA30_AUDIOCIF_CTRL_MONO_CONV_SHIFT);
regmap_write(regmap, reg, value);
}
EXPORT_SYMBOL_GPL(tegra124_ahub_set_cif);
MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>");
MODULE_DESCRIPTION("Tegra30 AHUB driver");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:" DRV_NAME);
+525
View File
@@ -0,0 +1,525 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* tegra30_ahub.h - Definitions for Tegra30 AHUB driver
*
* Copyright (c) 2011,2012, NVIDIA CORPORATION. All rights reserved.
*/
#ifndef __TEGRA30_AHUB_H__
#define __TEGRA30_AHUB_H__
/* Fields in *_CIF_RX/TX_CTRL; used by AHUB FIFOs, and all other audio modules */
#define TEGRA30_AUDIOCIF_CTRL_FIFO_THRESHOLD_SHIFT 28
#define TEGRA30_AUDIOCIF_CTRL_FIFO_THRESHOLD_MASK_US 0xf
#define TEGRA30_AUDIOCIF_CTRL_FIFO_THRESHOLD_MASK (TEGRA30_AUDIOCIF_CTRL_FIFO_THRESHOLD_MASK_US << TEGRA30_AUDIOCIF_CTRL_FIFO_THRESHOLD_SHIFT)
#define TEGRA124_AUDIOCIF_CTRL_FIFO_THRESHOLD_SHIFT 24
#define TEGRA124_AUDIOCIF_CTRL_FIFO_THRESHOLD_MASK_US 0x3f
#define TEGRA124_AUDIOCIF_CTRL_FIFO_THRESHOLD_MASK (TEGRA124_AUDIOCIF_CTRL_FIFO_THRESHOLD_MASK_US << TEGRA124_AUDIOCIF_CTRL_FIFO_THRESHOLD_SHIFT)
/* Channel count minus 1 */
#define TEGRA30_AUDIOCIF_CTRL_AUDIO_CHANNELS_SHIFT 24
#define TEGRA30_AUDIOCIF_CTRL_AUDIO_CHANNELS_MASK_US 7
#define TEGRA30_AUDIOCIF_CTRL_AUDIO_CHANNELS_MASK (TEGRA30_AUDIOCIF_CTRL_AUDIO_CHANNELS_MASK_US << TEGRA30_AUDIOCIF_CTRL_AUDIO_CHANNELS_SHIFT)
/* Channel count minus 1 */
#define TEGRA124_AUDIOCIF_CTRL_AUDIO_CHANNELS_SHIFT 20
#define TEGRA124_AUDIOCIF_CTRL_AUDIO_CHANNELS_MASK_US 0xf
#define TEGRA124_AUDIOCIF_CTRL_AUDIO_CHANNELS_MASK (TEGRA124_AUDIOCIF_CTRL_AUDIO_CHANNELS_MASK_US << TEGRA124_AUDIOCIF_CTRL_AUDIO_CHANNELS_SHIFT)
/* Channel count minus 1 */
#define TEGRA30_AUDIOCIF_CTRL_CLIENT_CHANNELS_SHIFT 16
#define TEGRA30_AUDIOCIF_CTRL_CLIENT_CHANNELS_MASK_US 7
#define TEGRA30_AUDIOCIF_CTRL_CLIENT_CHANNELS_MASK (TEGRA30_AUDIOCIF_CTRL_CLIENT_CHANNELS_MASK_US << TEGRA30_AUDIOCIF_CTRL_CLIENT_CHANNELS_SHIFT)
/* Channel count minus 1 */
#define TEGRA124_AUDIOCIF_CTRL_CLIENT_CHANNELS_SHIFT 16
#define TEGRA124_AUDIOCIF_CTRL_CLIENT_CHANNELS_MASK_US 0xf
#define TEGRA124_AUDIOCIF_CTRL_CLIENT_CHANNELS_MASK (TEGRA30_AUDIOCIF_CTRL_CLIENT_CHANNELS_MASK_US << TEGRA30_AUDIOCIF_CTRL_CLIENT_CHANNELS_SHIFT)
#define TEGRA30_AUDIOCIF_BITS_4 0
#define TEGRA30_AUDIOCIF_BITS_8 1
#define TEGRA30_AUDIOCIF_BITS_12 2
#define TEGRA30_AUDIOCIF_BITS_16 3
#define TEGRA30_AUDIOCIF_BITS_20 4
#define TEGRA30_AUDIOCIF_BITS_24 5
#define TEGRA30_AUDIOCIF_BITS_28 6
#define TEGRA30_AUDIOCIF_BITS_32 7
#define TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_SHIFT 12
#define TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_MASK (7 << TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_SHIFT)
#define TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_4 (TEGRA30_AUDIOCIF_BITS_4 << TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_SHIFT)
#define TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_8 (TEGRA30_AUDIOCIF_BITS_8 << TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_SHIFT)
#define TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_12 (TEGRA30_AUDIOCIF_BITS_12 << TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_SHIFT)
#define TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_16 (TEGRA30_AUDIOCIF_BITS_16 << TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_SHIFT)
#define TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_20 (TEGRA30_AUDIOCIF_BITS_20 << TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_SHIFT)
#define TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_24 (TEGRA30_AUDIOCIF_BITS_24 << TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_SHIFT)
#define TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_28 (TEGRA30_AUDIOCIF_BITS_28 << TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_SHIFT)
#define TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_32 (TEGRA30_AUDIOCIF_BITS_32 << TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_SHIFT)
#define TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_SHIFT 8
#define TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_MASK (7 << TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_SHIFT)
#define TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_4 (TEGRA30_AUDIOCIF_BITS_4 << TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_SHIFT)
#define TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_8 (TEGRA30_AUDIOCIF_BITS_8 << TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_SHIFT)
#define TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_12 (TEGRA30_AUDIOCIF_BITS_12 << TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_SHIFT)
#define TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_16 (TEGRA30_AUDIOCIF_BITS_16 << TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_SHIFT)
#define TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_20 (TEGRA30_AUDIOCIF_BITS_20 << TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_SHIFT)
#define TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_24 (TEGRA30_AUDIOCIF_BITS_24 << TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_SHIFT)
#define TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_28 (TEGRA30_AUDIOCIF_BITS_28 << TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_SHIFT)
#define TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_32 (TEGRA30_AUDIOCIF_BITS_32 << TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_SHIFT)
#define TEGRA30_AUDIOCIF_EXPAND_ZERO 0
#define TEGRA30_AUDIOCIF_EXPAND_ONE 1
#define TEGRA30_AUDIOCIF_EXPAND_LFSR 2
#define TEGRA30_AUDIOCIF_CTRL_EXPAND_SHIFT 6
#define TEGRA30_AUDIOCIF_CTRL_EXPAND_MASK (3 << TEGRA30_AUDIOCIF_CTRL_EXPAND_SHIFT)
#define TEGRA30_AUDIOCIF_CTRL_EXPAND_ZERO (TEGRA30_AUDIOCIF_EXPAND_ZERO << TEGRA30_AUDIOCIF_CTRL_EXPAND_SHIFT)
#define TEGRA30_AUDIOCIF_CTRL_EXPAND_ONE (TEGRA30_AUDIOCIF_EXPAND_ONE << TEGRA30_AUDIOCIF_CTRL_EXPAND_SHIFT)
#define TEGRA30_AUDIOCIF_CTRL_EXPAND_LFSR (TEGRA30_AUDIOCIF_EXPAND_LFSR << TEGRA30_AUDIOCIF_CTRL_EXPAND_SHIFT)
#define TEGRA30_AUDIOCIF_STEREO_CONV_CH0 0
#define TEGRA30_AUDIOCIF_STEREO_CONV_CH1 1
#define TEGRA30_AUDIOCIF_STEREO_CONV_AVG 2
#define TEGRA30_AUDIOCIF_CTRL_STEREO_CONV_SHIFT 4
#define TEGRA30_AUDIOCIF_CTRL_STEREO_CONV_MASK (3 << TEGRA30_AUDIOCIF_CTRL_STEREO_CONV_SHIFT)
#define TEGRA30_AUDIOCIF_CTRL_STEREO_CONV_CH0 (TEGRA30_AUDIOCIF_STEREO_CONV_CH0 << TEGRA30_AUDIOCIF_CTRL_STEREO_CONV_SHIFT)
#define TEGRA30_AUDIOCIF_CTRL_STEREO_CONV_CH1 (TEGRA30_AUDIOCIF_STEREO_CONV_CH1 << TEGRA30_AUDIOCIF_CTRL_STEREO_CONV_SHIFT)
#define TEGRA30_AUDIOCIF_CTRL_STEREO_CONV_AVG (TEGRA30_AUDIOCIF_STEREO_CONV_AVG << TEGRA30_AUDIOCIF_CTRL_STEREO_CONV_SHIFT)
#define TEGRA30_AUDIOCIF_CTRL_REPLICATE_SHIFT 3
#define TEGRA30_AUDIOCIF_DIRECTION_TX 0
#define TEGRA30_AUDIOCIF_DIRECTION_RX 1
#define TEGRA30_AUDIOCIF_CTRL_DIRECTION_SHIFT 2
#define TEGRA30_AUDIOCIF_CTRL_DIRECTION_MASK (1 << TEGRA30_AUDIOCIF_CTRL_DIRECTION_SHIFT)
#define TEGRA30_AUDIOCIF_CTRL_DIRECTION_TX (TEGRA30_AUDIOCIF_DIRECTION_TX << TEGRA30_AUDIOCIF_CTRL_DIRECTION_SHIFT)
#define TEGRA30_AUDIOCIF_CTRL_DIRECTION_RX (TEGRA30_AUDIOCIF_DIRECTION_RX << TEGRA30_AUDIOCIF_CTRL_DIRECTION_SHIFT)
#define TEGRA30_AUDIOCIF_TRUNCATE_ROUND 0
#define TEGRA30_AUDIOCIF_TRUNCATE_CHOP 1
#define TEGRA30_AUDIOCIF_CTRL_TRUNCATE_SHIFT 1
#define TEGRA30_AUDIOCIF_CTRL_TRUNCATE_MASK (1 << TEGRA30_AUDIOCIF_CTRL_TRUNCATE_SHIFT)
#define TEGRA30_AUDIOCIF_CTRL_TRUNCATE_ROUND (TEGRA30_AUDIOCIF_TRUNCATE_ROUND << TEGRA30_AUDIOCIF_CTRL_TRUNCATE_SHIFT)
#define TEGRA30_AUDIOCIF_CTRL_TRUNCATE_CHOP (TEGRA30_AUDIOCIF_TRUNCATE_CHOP << TEGRA30_AUDIOCIF_CTRL_TRUNCATE_SHIFT)
#define TEGRA30_AUDIOCIF_MONO_CONV_ZERO 0
#define TEGRA30_AUDIOCIF_MONO_CONV_COPY 1
#define TEGRA30_AUDIOCIF_CTRL_MONO_CONV_SHIFT 0
#define TEGRA30_AUDIOCIF_CTRL_MONO_CONV_MASK (1 << TEGRA30_AUDIOCIF_CTRL_MONO_CONV_SHIFT)
#define TEGRA30_AUDIOCIF_CTRL_MONO_CONV_ZERO (TEGRA30_AUDIOCIF_MONO_CONV_ZERO << TEGRA30_AUDIOCIF_CTRL_MONO_CONV_SHIFT)
#define TEGRA30_AUDIOCIF_CTRL_MONO_CONV_COPY (TEGRA30_AUDIOCIF_MONO_CONV_COPY << TEGRA30_AUDIOCIF_CTRL_MONO_CONV_SHIFT)
/* Registers within TEGRA30_AUDIO_CLUSTER_BASE */
/* TEGRA30_AHUB_CHANNEL_CTRL */
#define TEGRA30_AHUB_CHANNEL_CTRL 0x0
#define TEGRA30_AHUB_CHANNEL_CTRL_STRIDE 0x20
#define TEGRA30_AHUB_CHANNEL_CTRL_COUNT 4
#define TEGRA30_AHUB_CHANNEL_CTRL_TX_EN (1 << 31)
#define TEGRA30_AHUB_CHANNEL_CTRL_RX_EN (1 << 30)
#define TEGRA30_AHUB_CHANNEL_CTRL_LOOPBACK (1 << 29)
#define TEGRA30_AHUB_CHANNEL_CTRL_TX_THRESHOLD_SHIFT 16
#define TEGRA30_AHUB_CHANNEL_CTRL_TX_THRESHOLD_MASK_US 0xff
#define TEGRA30_AHUB_CHANNEL_CTRL_TX_THRESHOLD_MASK (TEGRA30_AHUB_CHANNEL_CTRL_TX_THRESHOLD_MASK_US << TEGRA30_AHUB_CHANNEL_CTRL_TX_THRESHOLD_SHIFT)
#define TEGRA30_AHUB_CHANNEL_CTRL_RX_THRESHOLD_SHIFT 8
#define TEGRA30_AHUB_CHANNEL_CTRL_RX_THRESHOLD_MASK_US 0xff
#define TEGRA30_AHUB_CHANNEL_CTRL_RX_THRESHOLD_MASK (TEGRA30_AHUB_CHANNEL_CTRL_RX_THRESHOLD_MASK_US << TEGRA30_AHUB_CHANNEL_CTRL_RX_THRESHOLD_SHIFT)
#define TEGRA30_AHUB_CHANNEL_CTRL_TX_PACK_EN (1 << 6)
#define TEGRA30_PACK_8_4 2
#define TEGRA30_PACK_16 3
#define TEGRA30_AHUB_CHANNEL_CTRL_TX_PACK_SHIFT 4
#define TEGRA30_AHUB_CHANNEL_CTRL_TX_PACK_MASK_US 3
#define TEGRA30_AHUB_CHANNEL_CTRL_TX_PACK_MASK (TEGRA30_AHUB_CHANNEL_CTRL_TX_PACK_MASK_US << TEGRA30_AHUB_CHANNEL_CTRL_TX_PACK_SHIFT)
#define TEGRA30_AHUB_CHANNEL_CTRL_TX_PACK_8_4 (TEGRA30_PACK_8_4 << TEGRA30_AHUB_CHANNEL_CTRL_TX_PACK_SHIFT)
#define TEGRA30_AHUB_CHANNEL_CTRL_TX_PACK_16 (TEGRA30_PACK_16 << TEGRA30_AHUB_CHANNEL_CTRL_TX_PACK_SHIFT)
#define TEGRA30_AHUB_CHANNEL_CTRL_RX_PACK_EN (1 << 2)
#define TEGRA30_AHUB_CHANNEL_CTRL_RX_PACK_SHIFT 0
#define TEGRA30_AHUB_CHANNEL_CTRL_RX_PACK_MASK_US 3
#define TEGRA30_AHUB_CHANNEL_CTRL_RX_PACK_MASK (TEGRA30_AHUB_CHANNEL_CTRL_RX_PACK_MASK_US << TEGRA30_AHUB_CHANNEL_CTRL_RX_PACK_SHIFT)
#define TEGRA30_AHUB_CHANNEL_CTRL_RX_PACK_8_4 (TEGRA30_PACK_8_4 << TEGRA30_AHUB_CHANNEL_CTRL_RX_PACK_SHIFT)
#define TEGRA30_AHUB_CHANNEL_CTRL_RX_PACK_16 (TEGRA30_PACK_16 << TEGRA30_AHUB_CHANNEL_CTRL_RX_PACK_SHIFT)
/* TEGRA30_AHUB_CHANNEL_CLEAR */
#define TEGRA30_AHUB_CHANNEL_CLEAR 0x4
#define TEGRA30_AHUB_CHANNEL_CLEAR_STRIDE 0x20
#define TEGRA30_AHUB_CHANNEL_CLEAR_COUNT 4
#define TEGRA30_AHUB_CHANNEL_CLEAR_TX_SOFT_RESET (1 << 31)
#define TEGRA30_AHUB_CHANNEL_CLEAR_RX_SOFT_RESET (1 << 30)
/* TEGRA30_AHUB_CHANNEL_STATUS */
#define TEGRA30_AHUB_CHANNEL_STATUS 0x8
#define TEGRA30_AHUB_CHANNEL_STATUS_STRIDE 0x20
#define TEGRA30_AHUB_CHANNEL_STATUS_COUNT 4
#define TEGRA30_AHUB_CHANNEL_STATUS_TX_FREE_SHIFT 24
#define TEGRA30_AHUB_CHANNEL_STATUS_TX_FREE_MASK_US 0xff
#define TEGRA30_AHUB_CHANNEL_STATUS_TX_FREE_MASK (TEGRA30_AHUB_CHANNEL_STATUS_TX_FREE_MASK_US << TEGRA30_AHUB_CHANNEL_STATUS_TX_FREE_SHIFT)
#define TEGRA30_AHUB_CHANNEL_STATUS_RX_FREE_SHIFT 16
#define TEGRA30_AHUB_CHANNEL_STATUS_RX_FREE_MASK_US 0xff
#define TEGRA30_AHUB_CHANNEL_STATUS_RX_FREE_MASK (TEGRA30_AHUB_CHANNEL_STATUS_RX_FREE_MASK_US << TEGRA30_AHUB_CHANNEL_STATUS_RX_FREE_SHIFT)
#define TEGRA30_AHUB_CHANNEL_STATUS_TX_TRIG (1 << 1)
#define TEGRA30_AHUB_CHANNEL_STATUS_RX_TRIG (1 << 0)
/* TEGRA30_AHUB_CHANNEL_TXFIFO */
#define TEGRA30_AHUB_CHANNEL_TXFIFO 0xc
#define TEGRA30_AHUB_CHANNEL_TXFIFO_STRIDE 0x20
#define TEGRA30_AHUB_CHANNEL_TXFIFO_COUNT 4
/* TEGRA30_AHUB_CHANNEL_RXFIFO */
#define TEGRA30_AHUB_CHANNEL_RXFIFO 0x10
#define TEGRA30_AHUB_CHANNEL_RXFIFO_STRIDE 0x20
#define TEGRA30_AHUB_CHANNEL_RXFIFO_COUNT 4
/* TEGRA30_AHUB_CIF_TX_CTRL */
#define TEGRA30_AHUB_CIF_TX_CTRL 0x14
#define TEGRA30_AHUB_CIF_TX_CTRL_STRIDE 0x20
#define TEGRA30_AHUB_CIF_TX_CTRL_COUNT 4
/* Uses field from TEGRA30_AUDIOCIF_CTRL_* */
/* TEGRA30_AHUB_CIF_RX_CTRL */
#define TEGRA30_AHUB_CIF_RX_CTRL 0x18
#define TEGRA30_AHUB_CIF_RX_CTRL_STRIDE 0x20
#define TEGRA30_AHUB_CIF_RX_CTRL_COUNT 4
/* Uses field from TEGRA30_AUDIOCIF_CTRL_* */
/* TEGRA30_AHUB_CONFIG_LINK_CTRL */
#define TEGRA30_AHUB_CONFIG_LINK_CTRL 0x80
#define TEGRA30_AHUB_CONFIG_LINK_CTRL_MASTER_FIFO_FULL_CNT_SHIFT 28
#define TEGRA30_AHUB_CONFIG_LINK_CTRL_MASTER_FIFO_FULL_CNT_MASK_US 0xf
#define TEGRA30_AHUB_CONFIG_LINK_CTRL_MASTER_FIFO_FULL_CNT_MASK (TEGRA30_AHUB_CONFIG_LINK_CTRL_MASTER_FIFO_FULL_CNT_MASK_US << TEGRA30_AHUB_CONFIG_LINK_CTRL_MASTER_FIFO_FULL_CNT_SHIFT)
#define TEGRA30_AHUB_CONFIG_LINK_CTRL_TIMEOUT_CNT_SHIFT 16
#define TEGRA30_AHUB_CONFIG_LINK_CTRL_TIMEOUT_CNT_MASK_US 0xfff
#define TEGRA30_AHUB_CONFIG_LINK_CTRL_TIMEOUT_CNT_MASK (TEGRA30_AHUB_CONFIG_LINK_CTRL_TIMEOUT_CNT_MASK_US << TEGRA30_AHUB_CONFIG_LINK_CTRL_TIMEOUT_CNT_SHIFT)
#define TEGRA30_AHUB_CONFIG_LINK_CTRL_IDLE_CNT_SHIFT 4
#define TEGRA30_AHUB_CONFIG_LINK_CTRL_IDLE_CNT_MASK_US 0xfff
#define TEGRA30_AHUB_CONFIG_LINK_CTRL_IDLE_CNT_MASK (TEGRA30_AHUB_CONFIG_LINK_CTRL_IDLE_CNT_MASK_US << TEGRA30_AHUB_CONFIG_LINK_CTRL_IDLE_CNT_SHIFT)
#define TEGRA30_AHUB_CONFIG_LINK_CTRL_CG_EN (1 << 2)
#define TEGRA30_AHUB_CONFIG_LINK_CTRL_CLEAR_TIMEOUT_CNTR (1 << 1)
#define TEGRA30_AHUB_CONFIG_LINK_CTRL_SOFT_RESET (1 << 0)
/* TEGRA30_AHUB_MISC_CTRL */
#define TEGRA30_AHUB_MISC_CTRL 0x84
#define TEGRA30_AHUB_MISC_CTRL_AUDIO_ACTIVE (1 << 31)
#define TEGRA30_AHUB_MISC_CTRL_AUDIO_CG_EN (1 << 8)
#define TEGRA30_AHUB_MISC_CTRL_AUDIO_OBS_SEL_SHIFT 0
#define TEGRA30_AHUB_MISC_CTRL_AUDIO_OBS_SEL_MASK (0x1f << TEGRA30_AHUB_MISC_CTRL_AUDIO_OBS_SEL_SHIFT)
/* TEGRA30_AHUB_APBDMA_LIVE_STATUS */
#define TEGRA30_AHUB_APBDMA_LIVE_STATUS 0x88
#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH3_RX_CIF_FIFO_FULL (1 << 31)
#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH3_TX_CIF_FIFO_FULL (1 << 30)
#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH2_RX_CIF_FIFO_FULL (1 << 29)
#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH2_TX_CIF_FIFO_FULL (1 << 28)
#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH1_RX_CIF_FIFO_FULL (1 << 27)
#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH1_TX_CIF_FIFO_FULL (1 << 26)
#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH0_RX_CIF_FIFO_FULL (1 << 25)
#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH0_TX_CIF_FIFO_FULL (1 << 24)
#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH3_RX_CIF_FIFO_EMPTY (1 << 23)
#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH3_TX_CIF_FIFO_EMPTY (1 << 22)
#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH2_RX_CIF_FIFO_EMPTY (1 << 21)
#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH2_TX_CIF_FIFO_EMPTY (1 << 20)
#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH1_RX_CIF_FIFO_EMPTY (1 << 19)
#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH1_TX_CIF_FIFO_EMPTY (1 << 18)
#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH0_RX_CIF_FIFO_EMPTY (1 << 17)
#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH0_TX_CIF_FIFO_EMPTY (1 << 16)
#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH3_RX_DMA_FIFO_FULL (1 << 15)
#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH3_TX_DMA_FIFO_FULL (1 << 14)
#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH2_RX_DMA_FIFO_FULL (1 << 13)
#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH2_TX_DMA_FIFO_FULL (1 << 12)
#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH1_RX_DMA_FIFO_FULL (1 << 11)
#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH1_TX_DMA_FIFO_FULL (1 << 10)
#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH0_RX_DMA_FIFO_FULL (1 << 9)
#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH0_TX_DMA_FIFO_FULL (1 << 8)
#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH3_RX_DMA_FIFO_EMPTY (1 << 7)
#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH3_TX_DMA_FIFO_EMPTY (1 << 6)
#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH2_RX_DMA_FIFO_EMPTY (1 << 5)
#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH2_TX_DMA_FIFO_EMPTY (1 << 4)
#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH1_RX_DMA_FIFO_EMPTY (1 << 3)
#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH1_TX_DMA_FIFO_EMPTY (1 << 2)
#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH0_RX_DMA_FIFO_EMPTY (1 << 1)
#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH0_TX_DMA_FIFO_EMPTY (1 << 0)
/* TEGRA30_AHUB_I2S_LIVE_STATUS */
#define TEGRA30_AHUB_I2S_LIVE_STATUS 0x8c
#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S4_RX_FIFO_FULL (1 << 29)
#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S4_TX_FIFO_FULL (1 << 28)
#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S3_RX_FIFO_FULL (1 << 27)
#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S3_TX_FIFO_FULL (1 << 26)
#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S2_RX_FIFO_FULL (1 << 25)
#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S2_TX_FIFO_FULL (1 << 24)
#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S1_RX_FIFO_FULL (1 << 23)
#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S1_TX_FIFO_FULL (1 << 22)
#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S0_RX_FIFO_FULL (1 << 21)
#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S0_TX_FIFO_FULL (1 << 20)
#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S4_RX_FIFO_ENABLED (1 << 19)
#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S4_TX_FIFO_ENABLED (1 << 18)
#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S3_RX_FIFO_ENABLED (1 << 17)
#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S3_TX_FIFO_ENABLED (1 << 16)
#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S2_RX_FIFO_ENABLED (1 << 15)
#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S2_TX_FIFO_ENABLED (1 << 14)
#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S1_RX_FIFO_ENABLED (1 << 13)
#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S1_TX_FIFO_ENABLED (1 << 12)
#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S0_RX_FIFO_ENABLED (1 << 11)
#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S0_TX_FIFO_ENABLED (1 << 10)
#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S4_RX_FIFO_EMPTY (1 << 9)
#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S4_TX_FIFO_EMPTY (1 << 8)
#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S3_RX_FIFO_EMPTY (1 << 7)
#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S3_TX_FIFO_EMPTY (1 << 6)
#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S2_RX_FIFO_EMPTY (1 << 5)
#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S2_TX_FIFO_EMPTY (1 << 4)
#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S1_RX_FIFO_EMPTY (1 << 3)
#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S1_TX_FIFO_EMPTY (1 << 2)
#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S0_RX_FIFO_EMPTY (1 << 1)
#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S0_TX_FIFO_EMPTY (1 << 0)
/* TEGRA30_AHUB_DAM0_LIVE_STATUS */
#define TEGRA30_AHUB_DAM_LIVE_STATUS 0x90
#define TEGRA30_AHUB_DAM_LIVE_STATUS_STRIDE 0x8
#define TEGRA30_AHUB_DAM_LIVE_STATUS_COUNT 3
#define TEGRA30_AHUB_DAM_LIVE_STATUS_TX_ENABLED (1 << 26)
#define TEGRA30_AHUB_DAM_LIVE_STATUS_RX1_ENABLED (1 << 25)
#define TEGRA30_AHUB_DAM_LIVE_STATUS_RX0_ENABLED (1 << 24)
#define TEGRA30_AHUB_DAM_LIVE_STATUS_TXFIFO_FULL (1 << 15)
#define TEGRA30_AHUB_DAM_LIVE_STATUS_RX1FIFO_FULL (1 << 9)
#define TEGRA30_AHUB_DAM_LIVE_STATUS_RX0FIFO_FULL (1 << 8)
#define TEGRA30_AHUB_DAM_LIVE_STATUS_TXFIFO_EMPTY (1 << 7)
#define TEGRA30_AHUB_DAM_LIVE_STATUS_RX1FIFO_EMPTY (1 << 1)
#define TEGRA30_AHUB_DAM_LIVE_STATUS_RX0FIFO_EMPTY (1 << 0)
/* TEGRA30_AHUB_SPDIF_LIVE_STATUS */
#define TEGRA30_AHUB_SPDIF_LIVE_STATUS 0xa8
#define TEGRA30_AHUB_SPDIF_LIVE_STATUS_USER_TX_ENABLED (1 << 11)
#define TEGRA30_AHUB_SPDIF_LIVE_STATUS_USER_RX_ENABLED (1 << 10)
#define TEGRA30_AHUB_SPDIF_LIVE_STATUS_DATA_TX_ENABLED (1 << 9)
#define TEGRA30_AHUB_SPDIF_LIVE_STATUS_DATA_RX_ENABLED (1 << 8)
#define TEGRA30_AHUB_SPDIF_LIVE_STATUS_USER_TXFIFO_FULL (1 << 7)
#define TEGRA30_AHUB_SPDIF_LIVE_STATUS_USER_RXFIFO_FULL (1 << 6)
#define TEGRA30_AHUB_SPDIF_LIVE_STATUS_DATA_TXFIFO_FULL (1 << 5)
#define TEGRA30_AHUB_SPDIF_LIVE_STATUS_DATA_RXFIFO_FULL (1 << 4)
#define TEGRA30_AHUB_SPDIF_LIVE_STATUS_USER_TXFIFO_EMPTY (1 << 3)
#define TEGRA30_AHUB_SPDIF_LIVE_STATUS_USER_RXFIFO_EMPTY (1 << 2)
#define TEGRA30_AHUB_SPDIF_LIVE_STATUS_DATA_TXFIFO_EMPTY (1 << 1)
#define TEGRA30_AHUB_SPDIF_LIVE_STATUS_DATA_RXFIFO_EMPTY (1 << 0)
/* TEGRA30_AHUB_I2S_INT_MASK */
#define TEGRA30_AHUB_I2S_INT_MASK 0xb0
/* TEGRA30_AHUB_DAM_INT_MASK */
#define TEGRA30_AHUB_DAM_INT_MASK 0xb4
/* TEGRA30_AHUB_SPDIF_INT_MASK */
#define TEGRA30_AHUB_SPDIF_INT_MASK 0xbc
/* TEGRA30_AHUB_APBIF_INT_MASK */
#define TEGRA30_AHUB_APBIF_INT_MASK 0xc0
/* TEGRA30_AHUB_I2S_INT_STATUS */
#define TEGRA30_AHUB_I2S_INT_STATUS 0xc8
/* TEGRA30_AHUB_DAM_INT_STATUS */
#define TEGRA30_AHUB_DAM_INT_STATUS 0xcc
/* TEGRA30_AHUB_SPDIF_INT_STATUS */
#define TEGRA30_AHUB_SPDIF_INT_STATUS 0xd4
/* TEGRA30_AHUB_APBIF_INT_STATUS */
#define TEGRA30_AHUB_APBIF_INT_STATUS 0xd8
/* TEGRA30_AHUB_I2S_INT_SOURCE */
#define TEGRA30_AHUB_I2S_INT_SOURCE 0xe0
/* TEGRA30_AHUB_DAM_INT_SOURCE */
#define TEGRA30_AHUB_DAM_INT_SOURCE 0xe4
/* TEGRA30_AHUB_SPDIF_INT_SOURCE */
#define TEGRA30_AHUB_SPDIF_INT_SOURCE 0xec
/* TEGRA30_AHUB_APBIF_INT_SOURCE */
#define TEGRA30_AHUB_APBIF_INT_SOURCE 0xf0
/* TEGRA30_AHUB_I2S_INT_SET */
#define TEGRA30_AHUB_I2S_INT_SET 0xf8
/* TEGRA30_AHUB_DAM_INT_SET */
#define TEGRA30_AHUB_DAM_INT_SET 0xfc
/* TEGRA30_AHUB_SPDIF_INT_SET */
#define TEGRA30_AHUB_SPDIF_INT_SET 0x100
/* TEGRA30_AHUB_APBIF_INT_SET */
#define TEGRA30_AHUB_APBIF_INT_SET 0x104
/* Registers within TEGRA30_AHUB_BASE */
#define TEGRA30_AHUB_AUDIO_RX 0x0
#define TEGRA30_AHUB_AUDIO_RX_STRIDE 0x4
#define TEGRA30_AHUB_AUDIO_RX_COUNT 17
/* This register repeats once for each entry in enum tegra30_ahub_rxcif */
/* The fields in this register are 1 bit per entry in tegra30_ahub_txcif */
/*
* Terminology:
* AHUB: Audio Hub; a cross-bar switch between the audio devices: DMA FIFOs,
* I2S controllers, SPDIF controllers, and DAMs.
* XBAR: The core cross-bar component of the AHUB.
* CIF: Client Interface; the HW module connecting an audio device to the
* XBAR.
* DAM: Digital Audio Mixer: A HW module that mixes multiple audio streams,
* possibly including sample-rate conversion.
*
* Each TX CIF transmits data into the XBAR. Each RX CIF can receive audio
* transmitted by a particular TX CIF.
*
* This driver is currently very simplistic; many HW features are not
* exposed; DAMs are not supported, only 16-bit stereo audio is supported,
* etc.
*/
enum tegra30_ahub_txcif {
TEGRA30_AHUB_TXCIF_APBIF_TX0,
TEGRA30_AHUB_TXCIF_APBIF_TX1,
TEGRA30_AHUB_TXCIF_APBIF_TX2,
TEGRA30_AHUB_TXCIF_APBIF_TX3,
TEGRA30_AHUB_TXCIF_I2S0_TX0,
TEGRA30_AHUB_TXCIF_I2S1_TX0,
TEGRA30_AHUB_TXCIF_I2S2_TX0,
TEGRA30_AHUB_TXCIF_I2S3_TX0,
TEGRA30_AHUB_TXCIF_I2S4_TX0,
TEGRA30_AHUB_TXCIF_DAM0_TX0,
TEGRA30_AHUB_TXCIF_DAM1_TX0,
TEGRA30_AHUB_TXCIF_DAM2_TX0,
TEGRA30_AHUB_TXCIF_SPDIF_TX0,
TEGRA30_AHUB_TXCIF_SPDIF_TX1,
};
enum tegra30_ahub_rxcif {
TEGRA30_AHUB_RXCIF_APBIF_RX0,
TEGRA30_AHUB_RXCIF_APBIF_RX1,
TEGRA30_AHUB_RXcIF_APBIF_RX2,
TEGRA30_AHUB_RXCIF_APBIF_RX3,
TEGRA30_AHUB_RXCIF_I2S0_RX0,
TEGRA30_AHUB_RXCIF_I2S1_RX0,
TEGRA30_AHUB_RXCIF_I2S2_RX0,
TEGRA30_AHUB_RXCIF_I2S3_RX0,
TEGRA30_AHUB_RXCIF_I2S4_RX0,
TEGRA30_AHUB_RXCIF_DAM0_RX0,
TEGRA30_AHUB_RXCIF_DAM0_RX1,
TEGRA30_AHUB_RXCIF_DAM1_RX0,
TEGRA30_AHUB_RXCIF_DAM2_RX1,
TEGRA30_AHUB_RXCIF_DAM3_RX0,
TEGRA30_AHUB_RXCIF_DAM3_RX1,
TEGRA30_AHUB_RXCIF_SPDIF_RX0,
TEGRA30_AHUB_RXCIF_SPDIF_RX1,
};
extern int tegra30_ahub_allocate_rx_fifo(enum tegra30_ahub_rxcif *rxcif,
char *dmachan, int dmachan_len,
dma_addr_t *fiforeg);
extern int tegra30_ahub_enable_rx_fifo(enum tegra30_ahub_rxcif rxcif);
extern int tegra30_ahub_disable_rx_fifo(enum tegra30_ahub_rxcif rxcif);
extern int tegra30_ahub_free_rx_fifo(enum tegra30_ahub_rxcif rxcif);
extern int tegra30_ahub_allocate_tx_fifo(enum tegra30_ahub_txcif *txcif,
char *dmachan, int dmachan_len,
dma_addr_t *fiforeg);
extern int tegra30_ahub_enable_tx_fifo(enum tegra30_ahub_txcif txcif);
extern int tegra30_ahub_disable_tx_fifo(enum tegra30_ahub_txcif txcif);
extern int tegra30_ahub_free_tx_fifo(enum tegra30_ahub_txcif txcif);
extern int tegra30_ahub_set_rx_cif_source(enum tegra30_ahub_rxcif rxcif,
enum tegra30_ahub_txcif txcif);
extern int tegra30_ahub_unset_rx_cif_source(enum tegra30_ahub_rxcif rxcif);
struct tegra30_ahub_cif_conf {
unsigned int threshold;
unsigned int audio_channels;
unsigned int client_channels;
unsigned int audio_bits;
unsigned int client_bits;
unsigned int expand;
unsigned int stereo_conv;
unsigned int replicate;
unsigned int direction;
unsigned int truncate;
unsigned int mono_conv;
};
void tegra30_ahub_set_cif(struct regmap *regmap, unsigned int reg,
struct tegra30_ahub_cif_conf *conf);
void tegra124_ahub_set_cif(struct regmap *regmap, unsigned int reg,
struct tegra30_ahub_cif_conf *conf);
struct tegra30_ahub_soc_data {
unsigned int num_resets;
void (*set_audio_cif)(struct regmap *regmap,
unsigned int reg,
struct tegra30_ahub_cif_conf *conf);
/*
* FIXME: There are many more differences in HW, such as:
* - More APBIF channels.
* - Extra separate chunks of register address space to represent
* the extra APBIF channels.
* - More units connected to the AHUB, so that tegra30_ahub_[rt]xcif
* need expansion, coupled with there being more defined bits in
* the AHUB routing registers.
* However, the driver doesn't support those new features yet, so we
* don't represent them here yet.
*/
};
struct tegra30_ahub {
const struct tegra30_ahub_soc_data *soc_data;
struct device *dev;
struct reset_control_bulk_data resets[21];
unsigned int nresets;
struct clk_bulk_data clocks[2];
unsigned int nclocks;
resource_size_t apbif_addr;
struct regmap *regmap_apbif;
struct regmap *regmap_ahub;
DECLARE_BITMAP(rx_usage, TEGRA30_AHUB_CHANNEL_CTRL_COUNT);
DECLARE_BITMAP(tx_usage, TEGRA30_AHUB_CHANNEL_CTRL_COUNT);
};
#endif
+570
View File
@@ -0,0 +1,570 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* tegra30_i2s.c - Tegra30 I2S driver
*
* Author: Stephen Warren <swarren@nvidia.com>
* Copyright (c) 2010-2012, NVIDIA CORPORATION. All rights reserved.
*
* Based on code copyright/by:
*
* Copyright (c) 2009-2010, NVIDIA Corporation.
* Scott Peterson <speterson@nvidia.com>
*
* Copyright (C) 2010 Google, Inc.
* Iliyan Malchev <malchev@google.com>
*/
#include <linux/clk.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include <linux/reset.h>
#include <linux/slab.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/dmaengine_pcm.h>
#include "tegra30_ahub.h"
#include "tegra30_i2s.h"
#define DRV_NAME "tegra30-i2s"
static int tegra30_i2s_runtime_suspend(struct device *dev)
{
struct tegra30_i2s *i2s = dev_get_drvdata(dev);
regcache_cache_only(i2s->regmap, true);
clk_disable_unprepare(i2s->clk_i2s);
return 0;
}
static int tegra30_i2s_runtime_resume(struct device *dev)
{
struct tegra30_i2s *i2s = dev_get_drvdata(dev);
int ret;
ret = clk_prepare_enable(i2s->clk_i2s);
if (ret) {
dev_err(dev, "clk_enable failed: %d\n", ret);
return ret;
}
regcache_cache_only(i2s->regmap, false);
regcache_mark_dirty(i2s->regmap);
ret = regcache_sync(i2s->regmap);
if (ret)
goto disable_clocks;
return 0;
disable_clocks:
clk_disable_unprepare(i2s->clk_i2s);
return ret;
}
static int tegra30_i2s_set_fmt(struct snd_soc_dai *dai,
unsigned int fmt)
{
struct tegra30_i2s *i2s = snd_soc_dai_get_drvdata(dai);
unsigned int mask = 0, val = 0;
switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
case SND_SOC_DAIFMT_NB_NF:
break;
default:
return -EINVAL;
}
mask |= TEGRA30_I2S_CTRL_MASTER_ENABLE;
switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
case SND_SOC_DAIFMT_BP_FP:
val |= TEGRA30_I2S_CTRL_MASTER_ENABLE;
break;
case SND_SOC_DAIFMT_BC_FC:
break;
default:
return -EINVAL;
}
mask |= TEGRA30_I2S_CTRL_FRAME_FORMAT_MASK |
TEGRA30_I2S_CTRL_LRCK_MASK;
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
case SND_SOC_DAIFMT_DSP_A:
val |= TEGRA30_I2S_CTRL_FRAME_FORMAT_FSYNC;
val |= TEGRA30_I2S_CTRL_LRCK_L_LOW;
break;
case SND_SOC_DAIFMT_DSP_B:
val |= TEGRA30_I2S_CTRL_FRAME_FORMAT_FSYNC;
val |= TEGRA30_I2S_CTRL_LRCK_R_LOW;
break;
case SND_SOC_DAIFMT_I2S:
val |= TEGRA30_I2S_CTRL_FRAME_FORMAT_LRCK;
val |= TEGRA30_I2S_CTRL_LRCK_L_LOW;
break;
case SND_SOC_DAIFMT_RIGHT_J:
val |= TEGRA30_I2S_CTRL_FRAME_FORMAT_LRCK;
val |= TEGRA30_I2S_CTRL_LRCK_L_LOW;
break;
case SND_SOC_DAIFMT_LEFT_J:
val |= TEGRA30_I2S_CTRL_FRAME_FORMAT_LRCK;
val |= TEGRA30_I2S_CTRL_LRCK_L_LOW;
break;
default:
return -EINVAL;
}
pm_runtime_get_sync(dai->dev);
regmap_update_bits(i2s->regmap, TEGRA30_I2S_CTRL, mask, val);
pm_runtime_put(dai->dev);
return 0;
}
static int tegra30_i2s_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
struct device *dev = dai->dev;
struct tegra30_i2s *i2s = snd_soc_dai_get_drvdata(dai);
unsigned int mask, val, reg;
int ret, sample_size, srate, i2sclock, bitcnt;
struct tegra30_ahub_cif_conf cif_conf;
if (params_channels(params) != 2)
return -EINVAL;
mask = TEGRA30_I2S_CTRL_BIT_SIZE_MASK;
switch (params_format(params)) {
case SNDRV_PCM_FORMAT_S16_LE:
val = TEGRA30_I2S_CTRL_BIT_SIZE_16;
sample_size = 16;
break;
default:
return -EINVAL;
}
regmap_update_bits(i2s->regmap, TEGRA30_I2S_CTRL, mask, val);
srate = params_rate(params);
/* Final "* 2" required by Tegra hardware */
i2sclock = srate * params_channels(params) * sample_size * 2;
bitcnt = (i2sclock / (2 * srate)) - 1;
if (bitcnt < 0 || bitcnt > TEGRA30_I2S_TIMING_CHANNEL_BIT_COUNT_MASK_US)
return -EINVAL;
ret = clk_set_rate(i2s->clk_i2s, i2sclock);
if (ret) {
dev_err(dev, "Can't set I2S clock rate: %d\n", ret);
return ret;
}
val = bitcnt << TEGRA30_I2S_TIMING_CHANNEL_BIT_COUNT_SHIFT;
if (i2sclock % (2 * srate))
val |= TEGRA30_I2S_TIMING_NON_SYM_ENABLE;
regmap_write(i2s->regmap, TEGRA30_I2S_TIMING, val);
cif_conf.threshold = 0;
cif_conf.audio_channels = 2;
cif_conf.client_channels = 2;
cif_conf.audio_bits = TEGRA30_AUDIOCIF_BITS_16;
cif_conf.client_bits = TEGRA30_AUDIOCIF_BITS_16;
cif_conf.expand = 0;
cif_conf.stereo_conv = 0;
cif_conf.replicate = 0;
cif_conf.truncate = 0;
cif_conf.mono_conv = 0;
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
cif_conf.direction = TEGRA30_AUDIOCIF_DIRECTION_RX;
reg = TEGRA30_I2S_CIF_RX_CTRL;
} else {
cif_conf.direction = TEGRA30_AUDIOCIF_DIRECTION_TX;
reg = TEGRA30_I2S_CIF_TX_CTRL;
}
i2s->soc_data->set_audio_cif(i2s->regmap, reg, &cif_conf);
val = (1 << TEGRA30_I2S_OFFSET_RX_DATA_OFFSET_SHIFT) |
(1 << TEGRA30_I2S_OFFSET_TX_DATA_OFFSET_SHIFT);
regmap_write(i2s->regmap, TEGRA30_I2S_OFFSET, val);
return 0;
}
static void tegra30_i2s_start_playback(struct tegra30_i2s *i2s)
{
tegra30_ahub_enable_tx_fifo(i2s->playback_fifo_cif);
regmap_update_bits(i2s->regmap, TEGRA30_I2S_CTRL,
TEGRA30_I2S_CTRL_XFER_EN_TX,
TEGRA30_I2S_CTRL_XFER_EN_TX);
}
static void tegra30_i2s_stop_playback(struct tegra30_i2s *i2s)
{
tegra30_ahub_disable_tx_fifo(i2s->playback_fifo_cif);
regmap_update_bits(i2s->regmap, TEGRA30_I2S_CTRL,
TEGRA30_I2S_CTRL_XFER_EN_TX, 0);
}
static void tegra30_i2s_start_capture(struct tegra30_i2s *i2s)
{
tegra30_ahub_enable_rx_fifo(i2s->capture_fifo_cif);
regmap_update_bits(i2s->regmap, TEGRA30_I2S_CTRL,
TEGRA30_I2S_CTRL_XFER_EN_RX,
TEGRA30_I2S_CTRL_XFER_EN_RX);
}
static void tegra30_i2s_stop_capture(struct tegra30_i2s *i2s)
{
regmap_update_bits(i2s->regmap, TEGRA30_I2S_CTRL,
TEGRA30_I2S_CTRL_XFER_EN_RX, 0);
tegra30_ahub_disable_rx_fifo(i2s->capture_fifo_cif);
}
static int tegra30_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
struct snd_soc_dai *dai)
{
struct tegra30_i2s *i2s = snd_soc_dai_get_drvdata(dai);
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
case SNDRV_PCM_TRIGGER_RESUME:
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
tegra30_i2s_start_playback(i2s);
else
tegra30_i2s_start_capture(i2s);
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
case SNDRV_PCM_TRIGGER_SUSPEND:
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
tegra30_i2s_stop_playback(i2s);
else
tegra30_i2s_stop_capture(i2s);
break;
default:
return -EINVAL;
}
return 0;
}
static int tegra30_i2s_set_tdm(struct snd_soc_dai *dai,
unsigned int tx_mask, unsigned int rx_mask,
int slots, int slot_width)
{
struct tegra30_i2s *i2s = snd_soc_dai_get_drvdata(dai);
unsigned int mask, val;
dev_dbg(dai->dev, "%s: txmask=0x%08x rxmask=0x%08x slots=%d width=%d\n",
__func__, tx_mask, rx_mask, slots, slot_width);
mask = TEGRA30_I2S_SLOT_CTRL_TOTAL_SLOTS_MASK |
TEGRA30_I2S_SLOT_CTRL_RX_SLOT_ENABLES_MASK |
TEGRA30_I2S_SLOT_CTRL_TX_SLOT_ENABLES_MASK;
val = (tx_mask << TEGRA30_I2S_SLOT_CTRL_TX_SLOT_ENABLES_SHIFT) |
(rx_mask << TEGRA30_I2S_SLOT_CTRL_RX_SLOT_ENABLES_SHIFT) |
((slots - 1) << TEGRA30_I2S_SLOT_CTRL_TOTAL_SLOTS_SHIFT);
pm_runtime_get_sync(dai->dev);
regmap_update_bits(i2s->regmap, TEGRA30_I2S_SLOT_CTRL, mask, val);
/* set the fsync width to minimum of 1 clock width */
regmap_update_bits(i2s->regmap, TEGRA30_I2S_CH_CTRL,
TEGRA30_I2S_CH_CTRL_FSYNC_WIDTH_MASK, 0x0);
pm_runtime_put(dai->dev);
return 0;
}
static int tegra30_i2s_probe(struct snd_soc_dai *dai)
{
struct tegra30_i2s *i2s = snd_soc_dai_get_drvdata(dai);
snd_soc_dai_init_dma_data(dai, &i2s->playback_dma_data,
&i2s->capture_dma_data);
return 0;
}
static const struct snd_soc_dai_ops tegra30_i2s_dai_ops = {
.probe = tegra30_i2s_probe,
.set_fmt = tegra30_i2s_set_fmt,
.hw_params = tegra30_i2s_hw_params,
.trigger = tegra30_i2s_trigger,
.set_tdm_slot = tegra30_i2s_set_tdm,
};
static const struct snd_soc_dai_driver tegra30_i2s_dai_template = {
.playback = {
.stream_name = "Playback",
.channels_min = 2,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_8000_96000,
.formats = SNDRV_PCM_FMTBIT_S16_LE,
},
.capture = {
.stream_name = "Capture",
.channels_min = 2,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_8000_96000,
.formats = SNDRV_PCM_FMTBIT_S16_LE,
},
.ops = &tegra30_i2s_dai_ops,
.symmetric_rate = 1,
};
static const struct snd_soc_component_driver tegra30_i2s_component = {
.name = DRV_NAME,
.legacy_dai_naming = 1,
};
static bool tegra30_i2s_wr_rd_reg(struct device *dev, unsigned int reg)
{
switch (reg) {
case TEGRA30_I2S_CTRL:
case TEGRA30_I2S_TIMING:
case TEGRA30_I2S_OFFSET:
case TEGRA30_I2S_CH_CTRL:
case TEGRA30_I2S_SLOT_CTRL:
case TEGRA30_I2S_CIF_RX_CTRL:
case TEGRA30_I2S_CIF_TX_CTRL:
case TEGRA30_I2S_FLOWCTL:
case TEGRA30_I2S_TX_STEP:
case TEGRA30_I2S_FLOW_STATUS:
case TEGRA30_I2S_FLOW_TOTAL:
case TEGRA30_I2S_FLOW_OVER:
case TEGRA30_I2S_FLOW_UNDER:
case TEGRA30_I2S_LCOEF_1_4_0:
case TEGRA30_I2S_LCOEF_1_4_1:
case TEGRA30_I2S_LCOEF_1_4_2:
case TEGRA30_I2S_LCOEF_1_4_3:
case TEGRA30_I2S_LCOEF_1_4_4:
case TEGRA30_I2S_LCOEF_1_4_5:
case TEGRA30_I2S_LCOEF_2_4_0:
case TEGRA30_I2S_LCOEF_2_4_1:
case TEGRA30_I2S_LCOEF_2_4_2:
return true;
default:
return false;
}
}
static bool tegra30_i2s_volatile_reg(struct device *dev, unsigned int reg)
{
switch (reg) {
case TEGRA30_I2S_FLOW_STATUS:
case TEGRA30_I2S_FLOW_TOTAL:
case TEGRA30_I2S_FLOW_OVER:
case TEGRA30_I2S_FLOW_UNDER:
return true;
default:
return false;
}
}
static const struct regmap_config tegra30_i2s_regmap_config = {
.reg_bits = 32,
.reg_stride = 4,
.val_bits = 32,
.max_register = TEGRA30_I2S_LCOEF_2_4_2,
.writeable_reg = tegra30_i2s_wr_rd_reg,
.readable_reg = tegra30_i2s_wr_rd_reg,
.volatile_reg = tegra30_i2s_volatile_reg,
.cache_type = REGCACHE_FLAT,
};
static const struct tegra30_i2s_soc_data tegra30_i2s_config = {
.set_audio_cif = tegra30_ahub_set_cif,
};
static const struct tegra30_i2s_soc_data tegra124_i2s_config = {
.set_audio_cif = tegra124_ahub_set_cif,
};
static const struct of_device_id tegra30_i2s_of_match[] = {
{ .compatible = "nvidia,tegra124-i2s", .data = &tegra124_i2s_config },
{ .compatible = "nvidia,tegra30-i2s", .data = &tegra30_i2s_config },
{},
};
MODULE_DEVICE_TABLE(of, tegra30_i2s_of_match);
static int tegra30_i2s_platform_probe(struct platform_device *pdev)
{
struct tegra30_i2s *i2s;
const struct tegra30_i2s_soc_data *soc_data;
u32 cif_ids[2];
void __iomem *regs;
int ret;
i2s = devm_kzalloc(&pdev->dev, sizeof(struct tegra30_i2s), GFP_KERNEL);
if (!i2s) {
ret = -ENOMEM;
goto err;
}
dev_set_drvdata(&pdev->dev, i2s);
soc_data = of_device_get_match_data(&pdev->dev);
if (!soc_data) {
dev_err(&pdev->dev, "Error: No device match found\n");
ret = -ENODEV;
goto err;
}
i2s->soc_data = soc_data;
i2s->dai = tegra30_i2s_dai_template;
i2s->dai.name = dev_name(&pdev->dev);
ret = of_property_read_u32_array(pdev->dev.of_node,
"nvidia,ahub-cif-ids", cif_ids,
ARRAY_SIZE(cif_ids));
if (ret < 0)
goto err;
i2s->playback_i2s_cif = cif_ids[0];
i2s->capture_i2s_cif = cif_ids[1];
i2s->clk_i2s = devm_clk_get(&pdev->dev, NULL);
if (IS_ERR(i2s->clk_i2s)) {
dev_err(&pdev->dev, "Can't retrieve i2s clock\n");
ret = PTR_ERR(i2s->clk_i2s);
goto err;
}
regs = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(regs)) {
ret = PTR_ERR(regs);
goto err;
}
i2s->regmap = devm_regmap_init_mmio(&pdev->dev, regs,
&tegra30_i2s_regmap_config);
if (IS_ERR(i2s->regmap)) {
dev_err(&pdev->dev, "regmap init failed\n");
ret = PTR_ERR(i2s->regmap);
goto err;
}
regcache_cache_only(i2s->regmap, true);
pm_runtime_enable(&pdev->dev);
i2s->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
i2s->playback_dma_data.maxburst = 4;
ret = tegra30_ahub_allocate_tx_fifo(&i2s->playback_fifo_cif,
i2s->playback_dma_chan,
sizeof(i2s->playback_dma_chan),
&i2s->playback_dma_data.addr);
if (ret) {
dev_err(&pdev->dev, "Could not alloc TX FIFO: %d\n", ret);
goto err_pm_disable;
}
ret = tegra30_ahub_set_rx_cif_source(i2s->playback_i2s_cif,
i2s->playback_fifo_cif);
if (ret) {
dev_err(&pdev->dev, "Could not route TX FIFO: %d\n", ret);
goto err_free_tx_fifo;
}
i2s->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
i2s->capture_dma_data.maxburst = 4;
ret = tegra30_ahub_allocate_rx_fifo(&i2s->capture_fifo_cif,
i2s->capture_dma_chan,
sizeof(i2s->capture_dma_chan),
&i2s->capture_dma_data.addr);
if (ret) {
dev_err(&pdev->dev, "Could not alloc RX FIFO: %d\n", ret);
goto err_unroute_tx_fifo;
}
ret = tegra30_ahub_set_rx_cif_source(i2s->capture_fifo_cif,
i2s->capture_i2s_cif);
if (ret) {
dev_err(&pdev->dev, "Could not route TX FIFO: %d\n", ret);
goto err_free_rx_fifo;
}
ret = snd_soc_register_component(&pdev->dev, &tegra30_i2s_component,
&i2s->dai, 1);
if (ret) {
dev_err(&pdev->dev, "Could not register DAI: %d\n", ret);
ret = -ENOMEM;
goto err_unroute_rx_fifo;
}
ret = tegra_pcm_platform_register_with_chan_names(&pdev->dev,
&i2s->dma_config, i2s->playback_dma_chan,
i2s->capture_dma_chan);
if (ret) {
dev_err(&pdev->dev, "Could not register PCM: %d\n", ret);
goto err_unregister_component;
}
return 0;
err_unregister_component:
snd_soc_unregister_component(&pdev->dev);
err_unroute_rx_fifo:
tegra30_ahub_unset_rx_cif_source(i2s->capture_fifo_cif);
err_free_rx_fifo:
tegra30_ahub_free_rx_fifo(i2s->capture_fifo_cif);
err_unroute_tx_fifo:
tegra30_ahub_unset_rx_cif_source(i2s->playback_i2s_cif);
err_free_tx_fifo:
tegra30_ahub_free_tx_fifo(i2s->playback_fifo_cif);
err_pm_disable:
pm_runtime_disable(&pdev->dev);
err:
return ret;
}
static void tegra30_i2s_platform_remove(struct platform_device *pdev)
{
struct tegra30_i2s *i2s = dev_get_drvdata(&pdev->dev);
tegra_pcm_platform_unregister(&pdev->dev);
snd_soc_unregister_component(&pdev->dev);
tegra30_ahub_unset_rx_cif_source(i2s->capture_fifo_cif);
tegra30_ahub_free_rx_fifo(i2s->capture_fifo_cif);
tegra30_ahub_unset_rx_cif_source(i2s->playback_i2s_cif);
tegra30_ahub_free_tx_fifo(i2s->playback_fifo_cif);
pm_runtime_disable(&pdev->dev);
}
static const struct dev_pm_ops tegra30_i2s_pm_ops = {
RUNTIME_PM_OPS(tegra30_i2s_runtime_suspend,
tegra30_i2s_runtime_resume, NULL)
SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume)
};
static struct platform_driver tegra30_i2s_driver = {
.driver = {
.name = DRV_NAME,
.of_match_table = tegra30_i2s_of_match,
.pm = pm_ptr(&tegra30_i2s_pm_ops),
},
.probe = tegra30_i2s_platform_probe,
.remove = tegra30_i2s_platform_remove,
};
module_platform_driver(tegra30_i2s_driver);
MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>");
MODULE_DESCRIPTION("Tegra30 I2S ASoC driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:" DRV_NAME);
+240
View File
@@ -0,0 +1,240 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* tegra30_i2s.h - Definitions for Tegra30 I2S driver
*
* Copyright (c) 2011,2012, NVIDIA CORPORATION. All rights reserved.
*/
#ifndef __TEGRA30_I2S_H__
#define __TEGRA30_I2S_H__
#include "tegra_pcm.h"
/* Register offsets from TEGRA30_I2S*_BASE */
#define TEGRA30_I2S_CTRL 0x0
#define TEGRA30_I2S_TIMING 0x4
#define TEGRA30_I2S_OFFSET 0x08
#define TEGRA30_I2S_CH_CTRL 0x0c
#define TEGRA30_I2S_SLOT_CTRL 0x10
#define TEGRA30_I2S_CIF_RX_CTRL 0x14
#define TEGRA30_I2S_CIF_TX_CTRL 0x18
#define TEGRA30_I2S_FLOWCTL 0x1c
#define TEGRA30_I2S_TX_STEP 0x20
#define TEGRA30_I2S_FLOW_STATUS 0x24
#define TEGRA30_I2S_FLOW_TOTAL 0x28
#define TEGRA30_I2S_FLOW_OVER 0x2c
#define TEGRA30_I2S_FLOW_UNDER 0x30
#define TEGRA30_I2S_LCOEF_1_4_0 0x34
#define TEGRA30_I2S_LCOEF_1_4_1 0x38
#define TEGRA30_I2S_LCOEF_1_4_2 0x3c
#define TEGRA30_I2S_LCOEF_1_4_3 0x40
#define TEGRA30_I2S_LCOEF_1_4_4 0x44
#define TEGRA30_I2S_LCOEF_1_4_5 0x48
#define TEGRA30_I2S_LCOEF_2_4_0 0x4c
#define TEGRA30_I2S_LCOEF_2_4_1 0x50
#define TEGRA30_I2S_LCOEF_2_4_2 0x54
/* Fields in TEGRA30_I2S_CTRL */
#define TEGRA30_I2S_CTRL_XFER_EN_TX (1 << 31)
#define TEGRA30_I2S_CTRL_XFER_EN_RX (1 << 30)
#define TEGRA30_I2S_CTRL_CG_EN (1 << 29)
#define TEGRA30_I2S_CTRL_SOFT_RESET (1 << 28)
#define TEGRA30_I2S_CTRL_TX_FLOWCTL_EN (1 << 27)
#define TEGRA30_I2S_CTRL_OBS_SEL_SHIFT 24
#define TEGRA30_I2S_CTRL_OBS_SEL_MASK (7 << TEGRA30_I2S_CTRL_OBS_SEL_SHIFT)
#define TEGRA30_I2S_FRAME_FORMAT_LRCK 0
#define TEGRA30_I2S_FRAME_FORMAT_FSYNC 1
#define TEGRA30_I2S_CTRL_FRAME_FORMAT_SHIFT 12
#define TEGRA30_I2S_CTRL_FRAME_FORMAT_MASK (7 << TEGRA30_I2S_CTRL_FRAME_FORMAT_SHIFT)
#define TEGRA30_I2S_CTRL_FRAME_FORMAT_LRCK (TEGRA30_I2S_FRAME_FORMAT_LRCK << TEGRA30_I2S_CTRL_FRAME_FORMAT_SHIFT)
#define TEGRA30_I2S_CTRL_FRAME_FORMAT_FSYNC (TEGRA30_I2S_FRAME_FORMAT_FSYNC << TEGRA30_I2S_CTRL_FRAME_FORMAT_SHIFT)
#define TEGRA30_I2S_CTRL_MASTER_ENABLE (1 << 10)
#define TEGRA30_I2S_LRCK_LEFT_LOW 0
#define TEGRA30_I2S_LRCK_RIGHT_LOW 1
#define TEGRA30_I2S_CTRL_LRCK_SHIFT 9
#define TEGRA30_I2S_CTRL_LRCK_MASK (1 << TEGRA30_I2S_CTRL_LRCK_SHIFT)
#define TEGRA30_I2S_CTRL_LRCK_L_LOW (TEGRA30_I2S_LRCK_LEFT_LOW << TEGRA30_I2S_CTRL_LRCK_SHIFT)
#define TEGRA30_I2S_CTRL_LRCK_R_LOW (TEGRA30_I2S_LRCK_RIGHT_LOW << TEGRA30_I2S_CTRL_LRCK_SHIFT)
#define TEGRA30_I2S_CTRL_LPBK_ENABLE (1 << 8)
#define TEGRA30_I2S_BIT_CODE_LINEAR 0
#define TEGRA30_I2S_BIT_CODE_ULAW 1
#define TEGRA30_I2S_BIT_CODE_ALAW 2
#define TEGRA30_I2S_CTRL_BIT_CODE_SHIFT 4
#define TEGRA30_I2S_CTRL_BIT_CODE_MASK (3 << TEGRA30_I2S_CTRL_BIT_CODE_SHIFT)
#define TEGRA30_I2S_CTRL_BIT_CODE_LINEAR (TEGRA30_I2S_BIT_CODE_LINEAR << TEGRA30_I2S_CTRL_BIT_CODE_SHIFT)
#define TEGRA30_I2S_CTRL_BIT_CODE_ULAW (TEGRA30_I2S_BIT_CODE_ULAW << TEGRA30_I2S_CTRL_BIT_CODE_SHIFT)
#define TEGRA30_I2S_CTRL_BIT_CODE_ALAW (TEGRA30_I2S_BIT_CODE_ALAW << TEGRA30_I2S_CTRL_BIT_CODE_SHIFT)
#define TEGRA30_I2S_BITS_8 1
#define TEGRA30_I2S_BITS_12 2
#define TEGRA30_I2S_BITS_16 3
#define TEGRA30_I2S_BITS_20 4
#define TEGRA30_I2S_BITS_24 5
#define TEGRA30_I2S_BITS_28 6
#define TEGRA30_I2S_BITS_32 7
/* Sample container size; see {RX,TX}_MASK field in CH_CTRL below */
#define TEGRA30_I2S_CTRL_BIT_SIZE_SHIFT 0
#define TEGRA30_I2S_CTRL_BIT_SIZE_MASK (7 << TEGRA30_I2S_CTRL_BIT_SIZE_SHIFT)
#define TEGRA30_I2S_CTRL_BIT_SIZE_8 (TEGRA30_I2S_BITS_8 << TEGRA30_I2S_CTRL_BIT_SIZE_SHIFT)
#define TEGRA30_I2S_CTRL_BIT_SIZE_12 (TEGRA30_I2S_BITS_12 << TEGRA30_I2S_CTRL_BIT_SIZE_SHIFT)
#define TEGRA30_I2S_CTRL_BIT_SIZE_16 (TEGRA30_I2S_BITS_16 << TEGRA30_I2S_CTRL_BIT_SIZE_SHIFT)
#define TEGRA30_I2S_CTRL_BIT_SIZE_20 (TEGRA30_I2S_BITS_20 << TEGRA30_I2S_CTRL_BIT_SIZE_SHIFT)
#define TEGRA30_I2S_CTRL_BIT_SIZE_24 (TEGRA30_I2S_BITS_24 << TEGRA30_I2S_CTRL_BIT_SIZE_SHIFT)
#define TEGRA30_I2S_CTRL_BIT_SIZE_28 (TEGRA30_I2S_BITS_28 << TEGRA30_I2S_CTRL_BIT_SIZE_SHIFT)
#define TEGRA30_I2S_CTRL_BIT_SIZE_32 (TEGRA30_I2S_BITS_32 << TEGRA30_I2S_CTRL_BIT_SIZE_SHIFT)
/* Fields in TEGRA30_I2S_TIMING */
#define TEGRA30_I2S_TIMING_NON_SYM_ENABLE (1 << 12)
#define TEGRA30_I2S_TIMING_CHANNEL_BIT_COUNT_SHIFT 0
#define TEGRA30_I2S_TIMING_CHANNEL_BIT_COUNT_MASK_US 0x7ff
#define TEGRA30_I2S_TIMING_CHANNEL_BIT_COUNT_MASK (TEGRA30_I2S_TIMING_CHANNEL_BIT_COUNT_MASK_US << TEGRA30_I2S_TIMING_CHANNEL_BIT_COUNT_SHIFT)
/* Fields in TEGRA30_I2S_OFFSET */
#define TEGRA30_I2S_OFFSET_RX_DATA_OFFSET_SHIFT 16
#define TEGRA30_I2S_OFFSET_RX_DATA_OFFSET_MASK_US 0x7ff
#define TEGRA30_I2S_OFFSET_RX_DATA_OFFSET_MASK (TEGRA30_I2S_OFFSET_RX_DATA_OFFSET_MASK_US << TEGRA30_I2S_OFFSET_RX_DATA_OFFSET_SHIFT)
#define TEGRA30_I2S_OFFSET_TX_DATA_OFFSET_SHIFT 0
#define TEGRA30_I2S_OFFSET_TX_DATA_OFFSET_MASK_US 0x7ff
#define TEGRA30_I2S_OFFSET_TX_DATA_OFFSET_MASK (TEGRA30_I2S_OFFSET_TX_DATA_OFFSET_MASK_US << TEGRA30_I2S_OFFSET_TX_DATA_OFFSET_SHIFT)
/* Fields in TEGRA30_I2S_CH_CTRL */
/* (FSYNC width - 1) in bit clocks */
#define TEGRA30_I2S_CH_CTRL_FSYNC_WIDTH_SHIFT 24
#define TEGRA30_I2S_CH_CTRL_FSYNC_WIDTH_MASK_US 0xff
#define TEGRA30_I2S_CH_CTRL_FSYNC_WIDTH_MASK (TEGRA30_I2S_CH_CTRL_FSYNC_WIDTH_MASK_US << TEGRA30_I2S_CH_CTRL_FSYNC_WIDTH_SHIFT)
#define TEGRA30_I2S_HIGHZ_NO 0
#define TEGRA30_I2S_HIGHZ_YES 1
#define TEGRA30_I2S_HIGHZ_ON_HALF_BIT_CLK 2
#define TEGRA30_I2S_CH_CTRL_HIGHZ_CTRL_SHIFT 12
#define TEGRA30_I2S_CH_CTRL_HIGHZ_CTRL_MASK (3 << TEGRA30_I2S_CH_CTRL_HIGHZ_CTRL_SHIFT)
#define TEGRA30_I2S_CH_CTRL_HIGHZ_CTRL_NO (TEGRA30_I2S_HIGHZ_NO << TEGRA30_I2S_CH_CTRL_HIGHZ_CTRL_SHIFT)
#define TEGRA30_I2S_CH_CTRL_HIGHZ_CTRL_YES (TEGRA30_I2S_HIGHZ_YES << TEGRA30_I2S_CH_CTRL_HIGHZ_CTRL_SHIFT)
#define TEGRA30_I2S_CH_CTRL_HIGHZ_CTRL_ON_HALF_BIT_CLK (TEGRA30_I2S_HIGHZ_ON_HALF_BIT_CLK << TEGRA30_I2S_CH_CTRL_HIGHZ_CTRL_SHIFT)
#define TEGRA30_I2S_MSB_FIRST 0
#define TEGRA30_I2S_LSB_FIRST 1
#define TEGRA30_I2S_CH_CTRL_RX_BIT_ORDER_SHIFT 10
#define TEGRA30_I2S_CH_CTRL_RX_BIT_ORDER_MASK (1 << TEGRA30_I2S_CH_CTRL_RX_BIT_ORDER_SHIFT)
#define TEGRA30_I2S_CH_CTRL_RX_BIT_ORDER_MSB_FIRST (TEGRA30_I2S_MSB_FIRST << TEGRA30_I2S_CH_CTRL_RX_BIT_ORDER_SHIFT)
#define TEGRA30_I2S_CH_CTRL_RX_BIT_ORDER_LSB_FIRST (TEGRA30_I2S_LSB_FIRST << TEGRA30_I2S_CH_CTRL_RX_BIT_ORDER_SHIFT)
#define TEGRA30_I2S_CH_CTRL_TX_BIT_ORDER_SHIFT 9
#define TEGRA30_I2S_CH_CTRL_TX_BIT_ORDER_MASK (1 << TEGRA30_I2S_CH_CTRL_TX_BIT_ORDER_SHIFT)
#define TEGRA30_I2S_CH_CTRL_TX_BIT_ORDER_MSB_FIRST (TEGRA30_I2S_MSB_FIRST << TEGRA30_I2S_CH_CTRL_TX_BIT_ORDER_SHIFT)
#define TEGRA30_I2S_CH_CTRL_TX_BIT_ORDER_LSB_FIRST (TEGRA30_I2S_LSB_FIRST << TEGRA30_I2S_CH_CTRL_TX_BIT_ORDER_SHIFT)
#define TEGRA30_I2S_POS_EDGE 0
#define TEGRA30_I2S_NEG_EDGE 1
#define TEGRA30_I2S_CH_CTRL_EGDE_CTRL_SHIFT 8
#define TEGRA30_I2S_CH_CTRL_EGDE_CTRL_MASK (1 << TEGRA30_I2S_CH_CTRL_EGDE_CTRL_SHIFT)
#define TEGRA30_I2S_CH_CTRL_EGDE_CTRL_POS_EDGE (TEGRA30_I2S_POS_EDGE << TEGRA30_I2S_CH_CTRL_EGDE_CTRL_SHIFT)
#define TEGRA30_I2S_CH_CTRL_EGDE_CTRL_NEG_EDGE (TEGRA30_I2S_NEG_EDGE << TEGRA30_I2S_CH_CTRL_EGDE_CTRL_SHIFT)
/* Sample size is # bits from BIT_SIZE minus this field */
#define TEGRA30_I2S_CH_CTRL_RX_MASK_BITS_SHIFT 4
#define TEGRA30_I2S_CH_CTRL_RX_MASK_BITS_MASK_US 7
#define TEGRA30_I2S_CH_CTRL_RX_MASK_BITS_MASK (TEGRA30_I2S_CH_CTRL_RX_MASK_BITS_MASK_US << TEGRA30_I2S_CH_CTRL_RX_MASK_BITS_SHIFT)
#define TEGRA30_I2S_CH_CTRL_TX_MASK_BITS_SHIFT 0
#define TEGRA30_I2S_CH_CTRL_TX_MASK_BITS_MASK_US 7
#define TEGRA30_I2S_CH_CTRL_TX_MASK_BITS_MASK (TEGRA30_I2S_CH_CTRL_TX_MASK_BITS_MASK_US << TEGRA30_I2S_CH_CTRL_TX_MASK_BITS_SHIFT)
/* Fields in TEGRA30_I2S_SLOT_CTRL */
/* Number of slots in frame, minus 1 */
#define TEGRA30_I2S_SLOT_CTRL_TOTAL_SLOTS_SHIFT 16
#define TEGRA30_I2S_SLOT_CTRL_TOTAL_SLOTS_MASK_US 7
#define TEGRA30_I2S_SLOT_CTRL_TOTAL_SLOTS_MASK (TEGRA30_I2S_SLOT_CTRL_TOTAL_SLOTS_MASK_US << TEGRA30_I2S_SLOT_CTRL_TOTAL_SLOTS_SHIFT)
/* TDM mode slot enable bitmask */
#define TEGRA30_I2S_SLOT_CTRL_RX_SLOT_ENABLES_SHIFT 8
#define TEGRA30_I2S_SLOT_CTRL_RX_SLOT_ENABLES_MASK (0xff << TEGRA30_I2S_SLOT_CTRL_RX_SLOT_ENABLES_SHIFT)
#define TEGRA30_I2S_SLOT_CTRL_TX_SLOT_ENABLES_SHIFT 0
#define TEGRA30_I2S_SLOT_CTRL_TX_SLOT_ENABLES_MASK (0xff << TEGRA30_I2S_SLOT_CTRL_TX_SLOT_ENABLES_SHIFT)
/* Fields in TEGRA30_I2S_CIF_RX_CTRL */
/* Uses field from TEGRA30_AUDIOCIF_CTRL_* in tegra30_ahub.h */
/* Fields in TEGRA30_I2S_CIF_TX_CTRL */
/* Uses field from TEGRA30_AUDIOCIF_CTRL_* in tegra30_ahub.h */
/* Fields in TEGRA30_I2S_FLOWCTL */
#define TEGRA30_I2S_FILTER_LINEAR 0
#define TEGRA30_I2S_FILTER_QUAD 1
#define TEGRA30_I2S_FLOWCTL_FILTER_SHIFT 31
#define TEGRA30_I2S_FLOWCTL_FILTER_MASK (1 << TEGRA30_I2S_FLOWCTL_FILTER_SHIFT)
#define TEGRA30_I2S_FLOWCTL_FILTER_LINEAR (TEGRA30_I2S_FILTER_LINEAR << TEGRA30_I2S_FLOWCTL_FILTER_SHIFT)
#define TEGRA30_I2S_FLOWCTL_FILTER_QUAD (TEGRA30_I2S_FILTER_QUAD << TEGRA30_I2S_FLOWCTL_FILTER_SHIFT)
/* Fields in TEGRA30_I2S_TX_STEP */
#define TEGRA30_I2S_TX_STEP_SHIFT 0
#define TEGRA30_I2S_TX_STEP_MASK_US 0xffff
#define TEGRA30_I2S_TX_STEP_MASK (TEGRA30_I2S_TX_STEP_MASK_US << TEGRA30_I2S_TX_STEP_SHIFT)
/* Fields in TEGRA30_I2S_FLOW_STATUS */
#define TEGRA30_I2S_FLOW_STATUS_UNDERFLOW (1 << 31)
#define TEGRA30_I2S_FLOW_STATUS_OVERFLOW (1 << 30)
#define TEGRA30_I2S_FLOW_STATUS_MONITOR_INT_EN (1 << 4)
#define TEGRA30_I2S_FLOW_STATUS_COUNTER_CLR (1 << 3)
#define TEGRA30_I2S_FLOW_STATUS_MONITOR_CLR (1 << 2)
#define TEGRA30_I2S_FLOW_STATUS_COUNTER_EN (1 << 1)
#define TEGRA30_I2S_FLOW_STATUS_MONITOR_EN (1 << 0)
/*
* There are no fields in TEGRA30_I2S_FLOW_TOTAL, TEGRA30_I2S_FLOW_OVER,
* TEGRA30_I2S_FLOW_UNDER; they are counters taking the whole register.
*/
/* Fields in TEGRA30_I2S_LCOEF_* */
#define TEGRA30_I2S_LCOEF_COEF_SHIFT 0
#define TEGRA30_I2S_LCOEF_COEF_MASK_US 0xffff
#define TEGRA30_I2S_LCOEF_COEF_MASK (TEGRA30_I2S_LCOEF_COEF_MASK_US << TEGRA30_I2S_LCOEF_COEF_SHIFT)
struct tegra30_i2s_soc_data {
void (*set_audio_cif)(struct regmap *regmap,
unsigned int reg,
struct tegra30_ahub_cif_conf *conf);
};
struct tegra30_i2s {
const struct tegra30_i2s_soc_data *soc_data;
struct snd_soc_dai_driver dai;
int cif_id;
struct clk *clk_i2s;
enum tegra30_ahub_txcif capture_i2s_cif;
enum tegra30_ahub_rxcif capture_fifo_cif;
char capture_dma_chan[8];
struct snd_dmaengine_dai_dma_data capture_dma_data;
enum tegra30_ahub_rxcif playback_i2s_cif;
enum tegra30_ahub_txcif playback_fifo_cif;
char playback_dma_chan[8];
struct snd_dmaengine_dai_dma_data playback_dma_data;
struct regmap *regmap;
struct snd_dmaengine_pcm_config dma_config;
};
#endif
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,51 @@
/* SPDX-License-Identifier: GPL-2.0-only */
#ifndef __TEGRA_ASOC_MACHINE_H__
#define __TEGRA_ASOC_MACHINE_H__
struct clk;
struct gpio_desc;
struct snd_soc_card;
struct snd_soc_jack;
struct platform_device;
struct snd_soc_jack_gpio;
struct snd_soc_pcm_runtime;
struct tegra_asoc_data {
unsigned int (*mclk_rate)(unsigned int srate);
const char *codec_dev_name;
const char *hp_jack_name;
struct snd_soc_card *card;
unsigned int mclk_id;
bool hp_jack_gpio_active_low;
bool add_common_dapm_widgets;
bool add_common_controls;
bool add_common_snd_ops;
bool add_headset_jack;
bool add_mic_jack;
bool add_hp_jack;
bool set_ac97;
};
struct tegra_machine {
struct clk *clk_pll_a_out0;
struct clk *clk_pll_a;
struct clk *clk_cdev1;
unsigned int set_baseclock;
unsigned int set_mclk;
const struct tegra_asoc_data *asoc;
struct gpio_desc *gpiod_ext_mic_en;
struct gpio_desc *gpiod_int_mic_en;
struct gpio_desc *gpiod_spkr_en;
struct gpio_desc *gpiod_mic_det;
struct gpio_desc *gpiod_ear_sel;
struct gpio_desc *gpiod_hp_mute;
struct gpio_desc *gpiod_hp_det;
struct snd_soc_jack *mic_jack;
struct snd_soc_jack_gpio *hp_jack_gpio;
};
int tegra_asoc_machine_probe(struct platform_device *pdev);
int tegra_asoc_machine_init(struct snd_soc_pcm_runtime *rtd);
#endif
@@ -0,0 +1,281 @@
// SPDX-License-Identifier: GPL-2.0-only
// SPDX-FileCopyrightText: Copyright (c) 2020-2025 NVIDIA CORPORATION. All rights reserved.
//
// tegra_audio_graph_card.c - Audio Graph based Tegra Machine Driver
#include <linux/math64.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <sound/graph_card.h>
#include <sound/pcm_params.h>
#include <sound/soc-dai.h>
#define MAX_PLLA_OUT0_DIV 128
#define simple_to_tegra_priv(simple) \
container_of(simple, struct tegra_audio_priv, simple)
enum srate_type {
/*
* Sample rates multiple of 8000 Hz and below are supported:
* ( 8000, 16000, 32000, 48000, 96000, 192000 Hz )
*/
x8_RATE,
/*
* Sample rates multiple of 11025 Hz and below are supported:
* ( 11025, 22050, 44100, 88200, 176400 Hz )
*/
x11_RATE,
NUM_RATE_TYPE,
};
struct tegra_audio_priv {
struct simple_util_priv simple;
struct clk *clk_plla_out0;
struct clk *clk_plla;
};
/* Tegra audio chip data */
struct tegra_audio_cdata {
unsigned int plla_rates[NUM_RATE_TYPE];
unsigned int plla_out0_rates[NUM_RATE_TYPE];
};
static bool need_clk_update(struct snd_soc_dai *dai)
{
if (snd_soc_dai_is_dummy(dai) ||
!dai->driver->ops ||
!dai->driver->name)
return false;
if (strstr(dai->driver->name, "I2S") ||
strstr(dai->driver->name, "DMIC") ||
strstr(dai->driver->name, "DSPK"))
return true;
return false;
}
/* Setup PLL clock as per the given sample rate */
static int tegra_audio_graph_update_pll(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
struct simple_util_priv *simple = snd_soc_card_get_drvdata(rtd->card);
struct tegra_audio_priv *priv = simple_to_tegra_priv(simple);
struct device *dev = rtd->card->dev;
const struct tegra_audio_cdata *data = of_device_get_match_data(dev);
unsigned int plla_rate, plla_out0_rate, bclk;
unsigned int srate = params_rate(params);
int err;
switch (srate) {
case 11025:
case 22050:
case 44100:
case 88200:
case 176400:
plla_out0_rate = data->plla_out0_rates[x11_RATE];
plla_rate = data->plla_rates[x11_RATE];
break;
case 8000:
case 16000:
case 32000:
case 48000:
case 96000:
case 192000:
plla_out0_rate = data->plla_out0_rates[x8_RATE];
plla_rate = data->plla_rates[x8_RATE];
break;
default:
dev_err(rtd->card->dev, "Unsupported sample rate %u\n",
srate);
return -EINVAL;
}
/*
* Below is the clock relation:
*
* PLLA
* |
* |--> PLLA_OUT0
* |
* |---> I2S modules
* |
* |---> DMIC modules
* |
* |---> DSPK modules
*
*
* Default PLLA_OUT0 rate might be too high when I/O is running
* at minimum PCM configurations. This may result in incorrect
* clock rates and glitchy audio. The maximum divider is 128
* and any thing higher than that won't work. Thus reduce PLLA_OUT0
* to work for lower configurations.
*
* This problem is seen for I2S only, as DMIC and DSPK minimum
* clock requirements are under allowed divider limits.
*/
bclk = srate * params_channels(params) * params_width(params);
if (div_u64(plla_out0_rate, bclk) > MAX_PLLA_OUT0_DIV)
plla_out0_rate >>= 1;
dev_dbg(rtd->card->dev,
"Update clock rates: PLLA(= %u Hz) and PLLA_OUT0(= %u Hz)\n",
plla_rate, plla_out0_rate);
/* Set PLLA rate */
err = clk_set_rate(priv->clk_plla, plla_rate);
if (err) {
dev_err(rtd->card->dev,
"Can't set plla rate for %u, err: %d\n",
plla_rate, err);
return err;
}
/* Set PLLA_OUT0 rate */
err = clk_set_rate(priv->clk_plla_out0, plla_out0_rate);
if (err) {
dev_err(rtd->card->dev,
"Can't set plla_out0 rate %u, err: %d\n",
plla_out0_rate, err);
return err;
}
return err;
}
static int tegra_audio_graph_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
int err;
if (need_clk_update(cpu_dai)) {
err = tegra_audio_graph_update_pll(substream, params);
if (err)
return err;
}
return simple_util_hw_params(substream, params);
}
static const struct snd_soc_ops tegra_audio_graph_ops = {
.startup = simple_util_startup,
.shutdown = simple_util_shutdown,
.hw_params = tegra_audio_graph_hw_params,
};
static int tegra_audio_graph_card_probe(struct snd_soc_card *card)
{
struct simple_util_priv *simple = snd_soc_card_get_drvdata(card);
struct tegra_audio_priv *priv = simple_to_tegra_priv(simple);
int ret;
priv->clk_plla = devm_clk_get(card->dev, "pll_a");
if (IS_ERR(priv->clk_plla))
return dev_err_probe(card->dev, PTR_ERR(priv->clk_plla),
"can't retrieve clk pll_a\n");
priv->clk_plla_out0 = devm_clk_get(card->dev, "plla_out0");
if (IS_ERR(priv->clk_plla_out0))
return dev_err_probe(card->dev, PTR_ERR(priv->clk_plla_out0),
"can't retrieve clk plla_out0\n");
ret = graph_util_card_probe(card);
if (ret < 0)
return dev_err_probe(card->dev, ret, "graph_util_card_probe failed\n");
return ret;
}
static int tegra_audio_graph_probe(struct platform_device *pdev)
{
struct tegra_audio_priv *priv;
struct device *dev = &pdev->dev;
struct snd_soc_card *card;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
card = simple_priv_to_card(&priv->simple);
card->driver_name = "tegra-ape";
card->probe = tegra_audio_graph_card_probe;
/* audio_graph_parse_of() depends on below */
card->component_chaining = 1;
priv->simple.ops = &tegra_audio_graph_ops;
priv->simple.force_dpcm = 1;
return audio_graph_parse_of(&priv->simple, dev);
}
static const struct tegra_audio_cdata tegra210_data = {
/* PLLA */
.plla_rates[x8_RATE] = 368640000,
.plla_rates[x11_RATE] = 338688000,
/* PLLA_OUT0 */
.plla_out0_rates[x8_RATE] = 49152000,
.plla_out0_rates[x11_RATE] = 45158400,
};
static const struct tegra_audio_cdata tegra186_data = {
/* PLLA */
.plla_rates[x8_RATE] = 245760000,
.plla_rates[x11_RATE] = 270950400,
/* PLLA_OUT0 */
.plla_out0_rates[x8_RATE] = 49152000,
.plla_out0_rates[x11_RATE] = 45158400,
};
static const struct tegra_audio_cdata tegra238_data = {
/* PLLA */
.plla_rates[x8_RATE] = 1277952000,
.plla_rates[x11_RATE] = 1264435200,
/* PLLA_OUT0 */
.plla_out0_rates[x8_RATE] = 49152000,
.plla_out0_rates[x11_RATE] = 45158400,
};
static const struct tegra_audio_cdata tegra264_data = {
/* PLLA1 */
.plla_rates[x8_RATE] = 983040000,
.plla_rates[x11_RATE] = 993484800,
/* PLLA1_OUT1 */
.plla_out0_rates[x8_RATE] = 49152000,
.plla_out0_rates[x11_RATE] = 45158400,
};
static const struct of_device_id graph_of_tegra_match[] = {
{ .compatible = "nvidia,tegra210-audio-graph-card",
.data = &tegra210_data },
{ .compatible = "nvidia,tegra186-audio-graph-card",
.data = &tegra186_data },
{ .compatible = "nvidia,tegra238-audio-graph-card",
.data = &tegra238_data },
{ .compatible = "nvidia,tegra264-audio-graph-card",
.data = &tegra264_data },
{},
};
MODULE_DEVICE_TABLE(of, graph_of_tegra_match);
static struct platform_driver tegra_audio_graph_card = {
.driver = {
.name = "tegra-audio-graph-card",
.pm = &snd_soc_pm_ops,
.of_match_table = graph_of_tegra_match,
},
.probe = tegra_audio_graph_probe,
.remove = simple_util_remove,
};
module_platform_driver(tegra_audio_graph_card);
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("ASoC Tegra Audio Graph Sound Card");
MODULE_AUTHOR("Sameer Pujar <spujar@nvidia.com>");
+87
View File
@@ -0,0 +1,87 @@
/* SPDX-License-Identifier: GPL-2.0-only
* SPDX-FileCopyrightText: Copyright (c) 2020-2025 NVIDIA CORPORATION. All rights reserved.
*
* tegra_cif.h - TEGRA Audio CIF Programming
*
*/
#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
#define TEGRA264_ACIF_CTRL_AUDIO_BITS_SHIFT 11
#define TEGRA264_ACIF_CTRL_CLIENT_CH_SHIFT 14
#define TEGRA264_ACIF_CTRL_AUDIO_CH_SHIFT 19
/* 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);
}
static inline void tegra264_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) << TEGRA264_ACIF_CTRL_AUDIO_CH_SHIFT) |
((conf->client_ch - 1) << TEGRA264_ACIF_CTRL_CLIENT_CH_SHIFT) |
(conf->audio_bits << TEGRA264_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
@@ -0,0 +1,130 @@
// SPDX-License-Identifier: GPL-2.0-only
// SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES.
// All rights reserved.
//
// ADMA bandwidth calculation
#include <linux/interconnect.h>
#include <linux/module.h>
#include <sound/dmaengine_pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include "tegra_isomgr_bw.h"
#include "tegra210_admaif.h"
#define MAX_SAMPLE_RATE 192 /* KHz*/
#define MAX_BYTES_PER_SAMPLE 4
int tegra_isomgr_adma_setbw(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai, bool is_running)
{
struct device *dev = dai->dev;
struct tegra_admaif *admaif = snd_soc_dai_get_drvdata(dai);
struct tegra_adma_isomgr *adma_isomgr = admaif->adma_isomgr;
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_pcm *pcm = substream->pcm;
u32 type = substream->stream, bandwidth = 0;
int sample_bytes;
if (!adma_isomgr)
return 0;
if (!runtime || !pcm)
return -EINVAL;
if (pcm->device >= adma_isomgr->max_pcm_device) {
dev_err(dev, "%s: PCM device number %d is greater than %d\n", __func__,
pcm->device, adma_isomgr->max_pcm_device);
return -EINVAL;
}
/*
* No action if stream is running and bandwidth is already set or
* stream is not running and bandwidth is already reset
*/
if ((adma_isomgr->bw_per_dev[type][pcm->device] && is_running) ||
(!adma_isomgr->bw_per_dev[type][pcm->device] && !is_running))
return 0;
if (is_running) {
sample_bytes = snd_pcm_format_width(runtime->format) / 8;
if (sample_bytes < 0)
return sample_bytes;
/* KB/s kilo bytes per sec */
bandwidth = runtime->channels * (runtime->rate / 1000) *
sample_bytes;
}
scoped_guard(mutex, &adma_isomgr->mutex) {
if (is_running) {
if (bandwidth + adma_isomgr->current_bandwidth > adma_isomgr->max_bw)
bandwidth = adma_isomgr->max_bw - adma_isomgr->current_bandwidth;
adma_isomgr->current_bandwidth += bandwidth;
} else {
adma_isomgr->current_bandwidth -=
adma_isomgr->bw_per_dev[type][pcm->device];
}
}
adma_isomgr->bw_per_dev[type][pcm->device] = bandwidth;
dev_dbg(dev, "Setting up bandwidth to %d KBps\n", adma_isomgr->current_bandwidth);
return icc_set_bw(adma_isomgr->icc_path_handle,
adma_isomgr->current_bandwidth, adma_isomgr->max_bw);
}
int tegra_isomgr_adma_register(struct device *dev)
{
struct tegra_admaif *admaif = dev_get_drvdata(dev);
struct tegra_adma_isomgr *adma_isomgr;
int i;
adma_isomgr = devm_kzalloc(dev, sizeof(struct tegra_adma_isomgr), GFP_KERNEL);
if (!adma_isomgr)
return -ENOMEM;
adma_isomgr->icc_path_handle = devm_of_icc_get(dev, "write");
if (IS_ERR(adma_isomgr->icc_path_handle))
return dev_err_probe(dev, PTR_ERR(adma_isomgr->icc_path_handle),
"failed to acquire interconnect path\n");
/* Either INTERCONNECT config OR interconnect property is not defined */
if (!adma_isomgr->icc_path_handle) {
devm_kfree(dev, adma_isomgr);
return 0;
}
adma_isomgr->max_pcm_device = admaif->soc_data->num_ch;
adma_isomgr->max_bw = STREAM_TYPE * MAX_SAMPLE_RATE * MAX_BYTES_PER_SAMPLE *
admaif->soc_data->max_stream_ch * adma_isomgr->max_pcm_device;
for (i = 0; i < STREAM_TYPE; i++) {
adma_isomgr->bw_per_dev[i] = devm_kzalloc(dev, adma_isomgr->max_pcm_device *
sizeof(u32), GFP_KERNEL);
if (!adma_isomgr->bw_per_dev[i])
return -ENOMEM;
}
adma_isomgr->current_bandwidth = 0;
mutex_init(&adma_isomgr->mutex);
admaif->adma_isomgr = adma_isomgr;
return 0;
}
void tegra_isomgr_adma_unregister(struct device *dev)
{
struct tegra_admaif *admaif = dev_get_drvdata(dev);
if (!admaif->adma_isomgr)
return;
mutex_destroy(&admaif->adma_isomgr->mutex);
}
MODULE_AUTHOR("Mohan Kumar <mkumard@nvidia.com>");
MODULE_DESCRIPTION("Tegra ADMA Bandwidth Request driver");
MODULE_LICENSE("GPL");
@@ -0,0 +1,31 @@
/* SPDX-License-Identifier: GPL-2.0-only
* SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES.
* All rights reserved.
*
* tegra_isomgr_bw.h - Definitions for ADMA bandwidth calculation
*
*/
#ifndef __TEGRA_ISOMGR_BW_H__
#define __TEGRA_ISOMGR_BW_H__
/* Playback and Capture streams */
#define STREAM_TYPE 2
struct tegra_adma_isomgr {
/* Protect pcm devices bandwidth */
struct mutex mutex;
/* interconnect path handle */
struct icc_path *icc_path_handle;
u32 *bw_per_dev[STREAM_TYPE];
u32 current_bandwidth;
u32 max_pcm_device;
u32 max_bw;
};
int tegra_isomgr_adma_register(struct device *dev);
void tegra_isomgr_adma_unregister(struct device *dev);
int tegra_isomgr_adma_setbw(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai, bool is_running);
#endif
+225
View File
@@ -0,0 +1,225 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* tegra_pcm.c - Tegra PCM driver
*
* Author: Stephen Warren <swarren@nvidia.com>
* Copyright (C) 2010,2012 - NVIDIA, Inc.
*
* Based on code copyright/by:
*
* Copyright (c) 2009-2010, NVIDIA Corporation.
* Scott Peterson <speterson@nvidia.com>
* Vijay Mali <vmali@nvidia.com>
*
* Copyright (C) 2010 Google, Inc.
* Iliyan Malchev <malchev@google.com>
*/
#include <linux/module.h>
#include <linux/dma-mapping.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/dmaengine_pcm.h>
#include "tegra_pcm.h"
static const struct snd_pcm_hardware tegra_pcm_hardware = {
.info = SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_INTERLEAVED,
.period_bytes_min = 1024,
.period_bytes_max = PAGE_SIZE,
.periods_min = 2,
.periods_max = 8,
.buffer_bytes_max = PAGE_SIZE * 8,
.fifo_size = 4,
};
static const struct snd_dmaengine_pcm_config tegra_dmaengine_pcm_config = {
.pcm_hardware = &tegra_pcm_hardware,
.prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config,
.prealloc_buffer_size = PAGE_SIZE * 8,
};
int tegra_pcm_platform_register(struct device *dev)
{
return snd_dmaengine_pcm_register(dev, &tegra_dmaengine_pcm_config, 0);
}
EXPORT_SYMBOL_GPL(tegra_pcm_platform_register);
int devm_tegra_pcm_platform_register(struct device *dev)
{
return devm_snd_dmaengine_pcm_register(dev, &tegra_dmaengine_pcm_config, 0);
}
EXPORT_SYMBOL_GPL(devm_tegra_pcm_platform_register);
int tegra_pcm_platform_register_with_chan_names(struct device *dev,
struct snd_dmaengine_pcm_config *config,
char *txdmachan, char *rxdmachan)
{
*config = tegra_dmaengine_pcm_config;
config->dma_dev = dev->parent;
config->chan_names[0] = txdmachan;
config->chan_names[1] = rxdmachan;
return snd_dmaengine_pcm_register(dev, config, 0);
}
EXPORT_SYMBOL_GPL(tegra_pcm_platform_register_with_chan_names);
void tegra_pcm_platform_unregister(struct device *dev)
{
return snd_dmaengine_pcm_unregister(dev);
}
EXPORT_SYMBOL_GPL(tegra_pcm_platform_unregister);
int tegra_pcm_open(struct snd_soc_component *component,
struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
struct snd_dmaengine_dai_dma_data *dmap;
struct dma_chan *chan;
struct snd_soc_dai *cpu_dai = snd_soc_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_chan(cpu_dai->dev, dmap->chan_name);
if (IS_ERR(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;
}
/* Set wait time to 500ms by default */
substream->wait_time = 500;
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 = snd_soc_substream_to_rtd(substream);
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 = snd_soc_substream_to_rtd(substream);
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(snd_soc_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;
}
return 0;
}
EXPORT_SYMBOL_GPL(tegra_pcm_hw_params);
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_dma_allocate(struct device *dev, struct snd_soc_pcm_runtime *rtd,
size_t size)
{
struct snd_pcm *pcm = rtd->pcm;
int ret;
ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32));
if (ret < 0)
return ret;
return snd_pcm_set_fixed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV_WC, dev, size);
}
int tegra_pcm_new(struct snd_soc_component *component,
struct snd_soc_pcm_runtime *rtd)
{
struct device *dev = component->dev;
/*
* Fallback for backwards-compatibility with older device trees that
* have the iommus property in the virtual, top-level "sound" node.
*/
if (!of_property_present(dev->of_node, "iommus"))
dev = rtd->card->snd_card->dev;
return tegra_pcm_dma_allocate(dev, rtd, tegra_pcm_hardware.buffer_bytes_max);
}
EXPORT_SYMBOL_GPL(tegra_pcm_new);
MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>");
MODULE_DESCRIPTION("Tegra PCM ASoC driver");
MODULE_LICENSE("GPL");
+41
View File
@@ -0,0 +1,41 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* tegra_pcm.h - Definitions for Tegra PCM driver
*
* Author: Stephen Warren <swarren@nvidia.com>
* Copyright (C) 2010,2012 - NVIDIA, Inc.
*
* Based on code copyright/by:
*
* Copyright (c) 2009-2010, NVIDIA Corporation.
* Scott Peterson <speterson@nvidia.com>
*
* Copyright (C) 2010 Google, Inc.
* Iliyan Malchev <malchev@google.com>
*/
#ifndef __TEGRA_PCM_H__
#define __TEGRA_PCM_H__
#include <sound/dmaengine_pcm.h>
#include <sound/asound.h>
int tegra_pcm_new(struct snd_soc_component *component,
struct snd_soc_pcm_runtime *rtd);
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);
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 devm_tegra_pcm_platform_register(struct device *dev);
int tegra_pcm_platform_register_with_chan_names(struct device *dev,
struct snd_dmaengine_pcm_config *config,
char *txdmachan, char *rxdmachan);
void tegra_pcm_platform_unregister(struct device *dev);
#endif
+189
View File
@@ -0,0 +1,189 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* tegra_wm8903.c - Tegra machine ASoC driver for boards using WM8903 codec.
*
* Author: Stephen Warren <swarren@nvidia.com>
* Copyright (C) 2010-2012 - NVIDIA, Inc.
*
* Based on code copyright/by:
*
* (c) 2009, 2010 Nvidia Graphics Pvt. Ltd.
*
* Copyright 2007 Wolfson Microelectronics PLC.
* Author: Graeme Gregory
* graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com
*/
#include <linux/gpio/consumer.h>
#include <linux/of.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <sound/core.h>
#include <sound/jack.h>
#include <sound/soc.h>
#include "../codecs/wm8903.h"
#include "tegra_asoc_machine.h"
static struct snd_soc_jack_pin tegra_wm8903_mic_jack_pins[] = {
{ .pin = "Mic Jack", .mask = SND_JACK_MICROPHONE },
};
static unsigned int tegra_wm8903_mclk_rate(unsigned int srate)
{
unsigned int mclk;
switch (srate) {
case 64000:
case 88200:
case 96000:
mclk = 128 * srate;
break;
default:
mclk = 256 * srate;
break;
}
/* FIXME: Codec only requires >= 3MHz if OSR==0 */
while (mclk < 6000000)
mclk *= 2;
return mclk;
}
static int tegra_wm8903_init(struct snd_soc_pcm_runtime *rtd)
{
struct tegra_machine *machine = snd_soc_card_get_drvdata(rtd->card);
struct snd_soc_card *card = rtd->card;
struct snd_soc_dapm_context *dapm = snd_soc_card_to_dapm(card);
int err;
/*
* Older version of machine driver was ignoring GPIO polarity,
* forcing it to active-low. This means that all older device-trees
* which set the polarity to active-high are wrong and we need to fix
* them up.
*/
if (machine->asoc->hp_jack_gpio_active_low) {
bool active_low = gpiod_is_active_low(machine->gpiod_hp_det);
machine->hp_jack_gpio->invert = !active_low;
}
err = tegra_asoc_machine_init(rtd);
if (err)
return err;
if (!machine->gpiod_mic_det && machine->asoc->add_mic_jack) {
struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
struct snd_soc_component *component = codec_dai->component;
int shrt = 0;
err = snd_soc_card_jack_new_pins(rtd->card, "Mic Jack",
SND_JACK_MICROPHONE,
machine->mic_jack,
tegra_wm8903_mic_jack_pins,
ARRAY_SIZE(tegra_wm8903_mic_jack_pins));
if (err) {
dev_err(rtd->dev, "Mic Jack creation failed: %d\n", err);
return err;
}
if (of_property_read_bool(card->dev->of_node, "nvidia,headset"))
shrt = SND_JACK_MICROPHONE;
wm8903_mic_detect(component, machine->mic_jack,
SND_JACK_MICROPHONE, shrt);
}
snd_soc_dapm_force_enable_pin(dapm, "MICBIAS");
return 0;
}
static int tegra_wm8903_remove(struct snd_soc_card *card)
{
struct snd_soc_dai_link *link = &card->dai_link[0];
struct snd_soc_pcm_runtime *rtd = snd_soc_get_pcm_runtime(card, link);
struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
struct snd_soc_component *component = codec_dai->component;
wm8903_mic_detect(component, NULL, 0, 0);
return 0;
}
SND_SOC_DAILINK_DEFS(hifi,
DAILINK_COMP_ARRAY(COMP_EMPTY()),
DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "wm8903-hifi")),
DAILINK_COMP_ARRAY(COMP_EMPTY()));
static struct snd_soc_dai_link tegra_wm8903_dai = {
.name = "WM8903",
.stream_name = "WM8903 PCM",
.init = tegra_wm8903_init,
.dai_fmt = SND_SOC_DAIFMT_I2S |
SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBC_CFC,
SND_SOC_DAILINK_REG(hifi),
};
static struct snd_soc_card snd_soc_tegra_wm8903 = {
.components = "codec:wm8903",
.owner = THIS_MODULE,
.dai_link = &tegra_wm8903_dai,
.num_links = 1,
.remove = tegra_wm8903_remove,
.fully_routed = true,
};
/* older device-trees used wrong polarity for the headphones-detection GPIO */
static const struct tegra_asoc_data tegra_wm8903_data_legacy = {
.mclk_rate = tegra_wm8903_mclk_rate,
.card = &snd_soc_tegra_wm8903,
.hp_jack_gpio_active_low = true,
.add_common_dapm_widgets = true,
.add_common_controls = true,
.add_common_snd_ops = true,
.add_mic_jack = true,
.add_hp_jack = true,
};
static const struct tegra_asoc_data tegra_wm8903_data = {
.mclk_rate = tegra_wm8903_mclk_rate,
.card = &snd_soc_tegra_wm8903,
.add_common_dapm_widgets = true,
.add_common_controls = true,
.add_common_snd_ops = true,
.add_mic_jack = true,
.add_hp_jack = true,
};
static const struct of_device_id tegra_wm8903_of_match[] = {
{ .compatible = "ad,tegra-audio-plutux", .data = &tegra_wm8903_data_legacy },
{ .compatible = "ad,tegra-audio-wm8903-medcom-wide", .data = &tegra_wm8903_data_legacy },
{ .compatible = "ad,tegra-audio-wm8903-tec", .data = &tegra_wm8903_data_legacy },
{ .compatible = "nvidia,tegra-audio-wm8903-cardhu", .data = &tegra_wm8903_data_legacy },
{ .compatible = "nvidia,tegra-audio-wm8903-harmony", .data = &tegra_wm8903_data_legacy },
{ .compatible = "nvidia,tegra-audio-wm8903-picasso", .data = &tegra_wm8903_data_legacy },
{ .compatible = "nvidia,tegra-audio-wm8903-seaboard", .data = &tegra_wm8903_data_legacy },
{ .compatible = "nvidia,tegra-audio-wm8903-ventana", .data = &tegra_wm8903_data_legacy },
{ .compatible = "nvidia,tegra-audio-wm8903", .data = &tegra_wm8903_data },
{},
};
MODULE_DEVICE_TABLE(of, tegra_wm8903_of_match);
static struct platform_driver tegra_wm8903_driver = {
.driver = {
.name = "tegra-wm8903",
.of_match_table = tegra_wm8903_of_match,
.pm = &snd_soc_pm_ops,
},
.probe = tegra_asoc_machine_probe,
};
module_platform_driver(tegra_wm8903_driver);
MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>");
MODULE_DESCRIPTION("Tegra+WM8903 machine ASoC driver");
MODULE_LICENSE("GPL");
+165
View File
@@ -0,0 +1,165 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* tegra_wm8962.c - Tegra machine ASoC driver for boards using WM8962 codec.
*
* Copyright (C) 2021-2024 Jonas Schwöbel <jonasschwoebel@yahoo.de>
* Svyatoslav Ryhel <clamor95@gmail.com>
*
* Based on tegra_wm8903 code copyright/by:
*
* Author: Stephen Warren <swarren@nvidia.com>
* Copyright (C) 2010-2012 - NVIDIA, Inc.
*
* Based on code copyright/by:
*
* (c) 2009, 2010 Nvidia Graphics Pvt. Ltd.
*
* Copyright 2007 Wolfson Microelectronics PLC.
* Author: Graeme Gregory
* graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com
*/
#include <linux/gpio/consumer.h>
#include <linux/of.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <sound/core.h>
#include <sound/jack.h>
#include <sound/soc.h>
#include "../codecs/wm8962.h"
#include "tegra_asoc_machine.h"
static struct snd_soc_jack_pin tegra_wm8962_mic_jack_pins[] = {
{ .pin = "Mic Jack", .mask = SND_JACK_MICROPHONE },
};
static unsigned int tegra_wm8962_mclk_rate(unsigned int srate)
{
unsigned int mclk;
switch (srate) {
case 8000:
case 16000:
case 24000:
case 32000:
case 48000:
case 64000:
case 96000:
mclk = 12288000;
break;
case 11025:
case 22050:
case 44100:
case 88200:
mclk = 11289600;
break;
default:
mclk = 12000000;
break;
}
return mclk;
}
static int tegra_wm8962_init(struct snd_soc_pcm_runtime *rtd)
{
struct tegra_machine *machine = snd_soc_card_get_drvdata(rtd->card);
struct snd_soc_card *card = rtd->card;
struct snd_soc_dapm_context *dapm = snd_soc_card_to_dapm(card);
int err;
err = tegra_asoc_machine_init(rtd);
if (err)
return err;
if (!machine->gpiod_mic_det && machine->asoc->add_mic_jack) {
struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
struct snd_soc_component *component = codec_dai->component;
err = snd_soc_card_jack_new_pins(rtd->card, "Mic Jack",
SND_JACK_MICROPHONE,
machine->mic_jack,
tegra_wm8962_mic_jack_pins,
ARRAY_SIZE(tegra_wm8962_mic_jack_pins));
if (err) {
dev_err(rtd->dev, "Mic Jack creation failed: %d\n", err);
return err;
}
wm8962_mic_detect(component, machine->mic_jack);
}
snd_soc_dapm_force_enable_pin(dapm, "MICBIAS");
return 0;
}
static int tegra_wm8962_remove(struct snd_soc_card *card)
{
struct snd_soc_dai_link *link = &card->dai_link[0];
struct snd_soc_pcm_runtime *rtd = snd_soc_get_pcm_runtime(card, link);
struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
struct snd_soc_component *component = codec_dai->component;
wm8962_mic_detect(component, NULL);
return 0;
}
SND_SOC_DAILINK_DEFS(wm8962_hifi,
DAILINK_COMP_ARRAY(COMP_EMPTY()),
DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "wm8962")),
DAILINK_COMP_ARRAY(COMP_EMPTY()));
static struct snd_soc_dai_link tegra_wm8962_dai = {
.name = "WM8962",
.stream_name = "WM8962 PCM",
.init = tegra_wm8962_init,
.dai_fmt = SND_SOC_DAIFMT_I2S |
SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBC_CFC,
SND_SOC_DAILINK_REG(wm8962_hifi),
};
static struct snd_soc_card snd_soc_tegra_wm8962 = {
.components = "codec:wm8962",
.owner = THIS_MODULE,
.dai_link = &tegra_wm8962_dai,
.num_links = 1,
.remove = tegra_wm8962_remove,
.fully_routed = true,
};
static const struct tegra_asoc_data tegra_wm8962_data = {
.mclk_rate = tegra_wm8962_mclk_rate,
.card = &snd_soc_tegra_wm8962,
.add_common_dapm_widgets = true,
.add_common_controls = true,
.add_common_snd_ops = true,
.add_mic_jack = true,
.add_hp_jack = true,
};
static const struct of_device_id tegra_wm8962_of_match[] = {
{ .compatible = "nvidia,tegra-audio-wm8962", .data = &tegra_wm8962_data },
{},
};
MODULE_DEVICE_TABLE(of, tegra_wm8962_of_match);
static struct platform_driver tegra_wm8962_driver = {
.driver = {
.name = "tegra-wm8962",
.of_match_table = tegra_wm8962_of_match,
.pm = &snd_soc_pm_ops,
},
.probe = tegra_asoc_machine_probe,
};
module_platform_driver(tegra_wm8962_driver);
MODULE_AUTHOR("Jonas Schwöbel <jonasschwoebel@yahoo.de>");
MODULE_AUTHOR("Svyatoslav Ryhel <clamor95@gmail.com>");
MODULE_DESCRIPTION("Tegra+WM8962 machine ASoC driver");
MODULE_LICENSE("GPL");