// SPDX-License-Identifier: GPL-2.0-only
//
// tegra186_asrc.c - Tegra186 ASRC driver
//
// Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved.
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/mod_devicetable.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 "tegra186_asrc.h"
#include "tegra_cif.h"
#define ASRC_STREAM_SOURCE_SELECT(id) \
(TEGRA186_ASRC_CFG + ((id) * TEGRA186_ASRC_STREAM_STRIDE))
#define ASRC_STREAM_REG(reg, id) ((reg) + ((id) * TEGRA186_ASRC_STREAM_STRIDE))
#define ASRC_STREAM_REG_DEFAULTS(id) \
{ ASRC_STREAM_REG(TEGRA186_ASRC_CFG, id), \
(((id) + 1) << 4) }, \
{ ASRC_STREAM_REG(TEGRA186_ASRC_RATIO_INT_PART, id), \
0x1 }, \
{ ASRC_STREAM_REG(TEGRA186_ASRC_RATIO_FRAC_PART, id), \
0x0 }, \
{ ASRC_STREAM_REG(TEGRA186_ASRC_MUTE_UNMUTE_DURATION, id), \
0x400 }, \
{ ASRC_STREAM_REG(TEGRA186_ASRC_RX_CIF_CTRL, id), \
0x7500 }, \
{ ASRC_STREAM_REG(TEGRA186_ASRC_TX_CIF_CTRL, id), \
0x7500 }
static const struct reg_default tegra186_asrc_reg_defaults[] = {
ASRC_STREAM_REG_DEFAULTS(0),
ASRC_STREAM_REG_DEFAULTS(1),
ASRC_STREAM_REG_DEFAULTS(2),
ASRC_STREAM_REG_DEFAULTS(3),
ASRC_STREAM_REG_DEFAULTS(4),
ASRC_STREAM_REG_DEFAULTS(5),
{ TEGRA186_ASRC_GLOBAL_ENB, 0},
{ TEGRA186_ASRC_GLOBAL_SOFT_RESET, 0},
{ TEGRA186_ASRC_GLOBAL_CG, 0x1 },
{ TEGRA186_ASRC_GLOBAL_CFG, 0x0 },
{ TEGRA186_ASRC_GLOBAL_SCRATCH_ADDR, 0},
{ TEGRA186_ASRC_GLOBAL_SCRATCH_CFG, 0x0c207980 },
{ TEGRA186_ASRC_RATIO_UPD_RX_CIF_CTRL, 0x00115500 },
{ TEGRA186_ASRC_GLOBAL_INT_MASK, 0x0},
{ TEGRA186_ASRC_GLOBAL_INT_SET, 0x0},
{ TEGRA186_ASRC_GLOBAL_INT_CLEAR, 0x0},
{ TEGRA186_ASRC_GLOBAL_APR_CTRL, 0x0},
{ TEGRA186_ASRC_GLOBAL_APR_CTRL_ACCESS_CTRL, 0x0},
{ TEGRA186_ASRC_GLOBAL_DISARM_APR, 0x0},
{ TEGRA186_ASRC_GLOBAL_DISARM_APR_ACCESS_CTRL, 0x0},
{ TEGRA186_ASRC_GLOBAL_RATIO_WR_ACCESS, 0x0},
{ TEGRA186_ASRC_GLOBAL_RATIO_WR_ACCESS_CTRL, 0x0},
{ TEGRA186_ASRC_CYA, 0x0},
};
static void tegra186_asrc_lock_stream(struct tegra186_asrc *asrc,
unsigned int id)
{
regmap_write(asrc->regmap,
ASRC_STREAM_REG(TEGRA186_ASRC_RATIO_LOCK_STATUS,
id),
1);
}
static int __maybe_unused tegra186_asrc_runtime_suspend(struct device *dev)
{
struct tegra186_asrc *asrc = dev_get_drvdata(dev);
regcache_cache_only(asrc->regmap, true);
regcache_mark_dirty(asrc->regmap);
return 0;
}
static int __maybe_unused tegra186_asrc_runtime_resume(struct device *dev)
{
struct tegra186_asrc *asrc = dev_get_drvdata(dev);
int id;
regcache_cache_only(asrc->regmap, false);
/*
* Below sequence is recommended after a runtime PM cycle.
* This otherwise leads to transfer failures. The cache
* sync is done after this to restore other settings.
*/
regmap_write(asrc->regmap, TEGRA186_ASRC_GLOBAL_SCRATCH_ADDR,
TEGRA186_ASRC_ARAM_START_ADDR);
regmap_write(asrc->regmap, TEGRA186_ASRC_GLOBAL_ENB,
TEGRA186_ASRC_GLOBAL_EN);
regcache_sync(asrc->regmap);
for (id = 0; id < TEGRA186_ASRC_STREAM_MAX; id++) {
if (asrc->lane[id].ratio_source !=
TEGRA186_ASRC_RATIO_SOURCE_SW)
continue;
regmap_write(asrc->regmap,
ASRC_STREAM_REG(TEGRA186_ASRC_RATIO_INT_PART,
id),
asrc->lane[id].int_part);
regmap_write(asrc->regmap,
ASRC_STREAM_REG(TEGRA186_ASRC_RATIO_FRAC_PART,
id),
asrc->lane[id].frac_part);
tegra186_asrc_lock_stream(asrc, id);
}
return 0;
}
static int tegra186_asrc_set_audio_cif(struct tegra186_asrc *asrc,
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);
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 = TEGRA_ACIF_BITS_24;
tegra_set_cif(asrc->regmap, reg, &cif_conf);
return 0;
}
static int tegra186_asrc_in_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
struct device *dev = dai->dev;
struct tegra186_asrc *asrc = snd_soc_dai_get_drvdata(dai);
int ret, id = dai->id;
/* Set input threshold */
regmap_write(asrc->regmap,
ASRC_STREAM_REG(TEGRA186_ASRC_RX_THRESHOLD, dai->id),
asrc->lane[id].input_thresh);
ret = tegra186_asrc_set_audio_cif(asrc, params,
ASRC_STREAM_REG(TEGRA186_ASRC_RX_CIF_CTRL, dai->id));
if (ret) {
dev_err(dev, "Can't set ASRC RX%d CIF: %d\n", dai->id, ret);
return ret;
}
return ret;
}
static int tegra186_asrc_out_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
struct device *dev = dai->dev;
struct tegra186_asrc *asrc = snd_soc_dai_get_drvdata(dai);
int ret, id = dai->id - 7;
/* Set output threshold */
regmap_write<