Unverified Commit 7358a803 authored by Sameer Pujar's avatar Sameer Pujar Committed by Mark Brown
Browse files

ASoC: tegra: Add Tegra210 based OPE driver



The Output Processing Engine (OPE) is one of the AHUB client. It has
PEQ (Parametric Equalizer) and MBDRC (Multi Band Dynamic Range Compressor)
sub blocks for data processing. The PEQ block gets samples from the MBDRC
block.

This patch registers OPE driver with ASoC framework. The component driver
exposes DAPM widgets, routes and kcontrols for the device. The DAI driver
exposes OPE interfaces, which can be used to connect different components
in the ASoC layer. Makefile and Kconfig support is added to allow build
the driver.

Signed-off-by: default avatarSameer Pujar <spujar@nvidia.com>
Link: https://lore.kernel.org/r/1654238172-16293-3-git-send-email-spujar@nvidia.com


Signed-off-by: default avatarMark Brown <broonie@kernel.org>
parent 45c6c5e0
Loading
Loading
Loading
Loading
+9 −0
Original line number Diff line number Diff line
@@ -85,6 +85,15 @@ config SND_SOC_TEGRA210_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
+2 −0
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ snd-soc-tegra210-sfc-objs := tegra210_sfc.o
snd-soc-tegra210-amx-objs := tegra210_amx.o
snd-soc-tegra210-adx-objs := tegra210_adx.o
snd-soc-tegra210-mixer-objs := tegra210_mixer.o
snd-soc-tegra210-ope-objs := 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
@@ -38,6 +39,7 @@ 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-objs := tegra_wm8903.o
+1012 −0

File added.

Preview size limit exceeded, changes collapsed.

+215 −0
Original line number Diff line number Diff line
/* 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 fliter_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
+419 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0-only
//
// tegra210_ope.c - Tegra210 OPE 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_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_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_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_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_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_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_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_soc_kcontrol_component(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_soc_kcontrol_component(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),
	.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)) {
		dev_err(dev, "regmap init failed\n");
		return PTR_ERR(ope->regmap);
	}

	regcache_cache_only(ope->regmap, true);

	dev_set_drvdata(dev, ope);

	err = tegra210_peq_regmap_init(pdev);
	if (err < 0) {
		dev_err(dev, "PEQ init failed\n");
		return err;
	}

	err = tegra210_mbdrc_regmap_init(pdev);
	if (err < 0) {
		dev_err(dev, "MBDRC init failed\n");
		return err;
	}

	err = devm_snd_soc_register_component(dev, &tegra210_ope_cmpnt,
					      tegra210_ope_dais,
					      ARRAY_SIZE(tegra210_ope_dais));
	if (err) {
		dev_err(dev, "can't register OPE component, err: %d\n", err);
		return err;
	}

	pm_runtime_enable(dev);

	return 0;
}

static int tegra210_ope_remove(struct platform_device *pdev)
{
	pm_runtime_disable(&pdev->dev);

	return 0;
}

static int __maybe_unused 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 __maybe_unused 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 = {
	SET_RUNTIME_PM_OPS(tegra210_ope_runtime_suspend,
			   tegra210_ope_runtime_resume, NULL)
	SET_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 = &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");
Loading