// SPDX-License-Identifier: GPL-2.0-only
//
// tegra210_i2s.c - Tegra210 I2S driver
//
// Copyright (c) 2020 NVIDIA CORPORATION. All rights reserved.
#include <linux/clk.h>
#include <linux/device.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/of_graph.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/simple_card_utils.h>
#include <sound/soc.h>
#include "tegra210_i2s.h"
#include "tegra_cif.h"
static const struct reg_default tegra210_i2s_reg_defaults[] = {
{ TEGRA210_I2S_RX_INT_MASK, 0x00000003 },
{ TEGRA210_I2S_RX_CIF_CTRL, 0x00007700 },
{ TEGRA210_I2S_TX_INT_MASK, 0x00000003 },
{ TEGRA210_I2S_TX_CIF_CTRL, 0x00007700 },
{ TEGRA210_I2S_CG, 0x1 },
{ TEGRA210_I2S_TIMING, 0x0000001f },
{ TEGRA210_I2S_ENABLE, 0x1 },
/*
* Below update does not have any effect on Tegra186 and Tegra194.
* On Tegra210, I2S4 has "i2s4a" and "i2s4b" pins and below update
* is required to select i2s4b for it to be functional for I2S
* operation.
*/
{ TEGRA210_I2S_CYA, 0x1 },
};
static void tegra210_i2s_set_slot_ctrl(struct regmap *regmap,
unsigned int total_slots,
unsigned int tx_slot_mask,
unsigned int rx_slot_mask)
{
regmap_write(regmap, TEGRA210_I2S_SLOT_CTRL, total_slots - 1);
regmap_write(regmap, TEGRA210_I2S_TX_SLOT_CTRL, tx_slot_mask);
regmap_write(regmap, TEGRA210_I2S_RX_SLOT_CTRL, rx_slot_mask);
}
static int tegra210_i2s_set_clock_rate(struct device *dev,
unsigned int clock_rate)
{
struct tegra210_i2s *i2s = dev_get_drvdata(dev);
unsigned int val;
int err;
regmap_read(i2s->regmap, TEGRA210_I2S_CTRL, &val);
/* No need to set rates if I2S is being operated in slave */
if (!(val & I2S_CTRL_MASTER_EN))
return 0;
err = clk_set_rate(i2s->clk_i2s, clock_rate);
if (err) {
dev_err(dev, "can't set I2S bit clock rate %u, err: %d\n",
clock_rate, err);
return err;
}
if (!IS_ERR(i2s->clk_sync_input)) {
/*
* Other I/O modules in AHUB can use i2s bclk as reference
* clock. Below sets sync input clock rate as per bclk,
* which can be used as input to other I/O modules.
*/
err = clk_set_rate(i2s->clk_sync_input, clock_rate);
if (err) {
dev_err(dev,
"can't set I2S sync input rate %u, err = %d\n",
clock_rate, err);
return err;
}
}
return 0;
}
static int tegra210_i2s_sw_reset(struct snd_soc_component *compnt,
int stream)
{
struct device *dev = compnt->dev;
struct tegra210_i2s *i2s = dev_get_drvdata(dev);
unsigned int reset_mask = I2S_SOFT_RESET_MASK;
unsigned int reset_en = I2S_SOFT_RESET_EN;
unsigned int reset_reg, cif_reg, stream_reg;
unsigned int cif_ctrl, stream_ctrl, i2s_ctrl, val;
int err;
if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
reset_reg = TEGRA210_I2S_RX_SOFT_RESET;
cif_reg = TEGRA210_I2S_RX_CIF_CTRL;
stream_reg = TEGRA210_I2S_RX_CTRL;
} else {
reset_reg = TEGRA210_I2S_TX_SOFT_RESET;
cif_reg = TEGRA210_I2S_TX_CIF_CTRL;
stream_reg = TEGRA210_I2S_TX_CTRL;
}
/* Store CIF and I2S control values */
regmap_read(i2s->regmap, cif_reg, &cif_ctrl);
regmap_read(i2s->regmap, stream_reg, &stream_ctrl);
regmap_read(i2s->regmap, TEGRA210_I2S_CTRL, &i2s_ctrl);
/* Reset to make sure the previous transactions are clean */
regmap_update_bits(i2s->regmap, reset_reg, reset_mask, reset_en);
err = regmap_read_poll_timeout(i2s->regmap, reset_reg, val,
!(val & reset_mask & reset_en),
10, 10000);
if (err) {
dev_err(dev, "timeout: failed to reset I2S for %s\n",
snd_pcm_direction_name(stream));
return err;
}
/* Restore CIF and I2S control values */
regmap_write(i2s->regmap, cif_reg, cif_ctrl);
regmap_write(i2s->regmap, stream_reg, stream_ctrl);
regmap_write(i2s->regmap, TEGRA210_I2S_CTRL, i2s_ctrl);
return 0;
}
static int tegra210_i2s_init(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol, int event)
{
struct snd_soc_component *compnt = snd_soc_dapm_to_component(w->dapm);
struct device *dev = compnt->dev;
struct tegra210_i2s *i2s = dev_get_drvdata(dev);
unsigned int val, status_reg;
int stream;
int err;
switch (w->reg) {
case TEGRA210_I2S_RX_ENABLE:
stream = SNDRV_PCM_STREAM_PLAYBACK;
status_reg = TEGRA210_I2S_RX_STATUS;
break;
case TEGRA210_I2S_TX_ENABLE:
stream = SNDRV_PCM_STREAM_CAPTURE;
status_reg = TEGRA210_I2S_TX_STATUS;
break;
default:
return -EINVAL;
}
/* Ensure I2S is in disabled state before new session */
err = regmap_read_poll_timeout(i2s->regmap, status_reg, val,
!(val & I2S_EN_MASK & I2S_EN),
10, 10000);
if (err) {
dev_err(dev, "timeout: previous I2S %s is still active\n",
snd_pcm_direction_name(stream));
return err;
}
return tegra210_i2s_sw_reset(compnt, stream);
}
static int __maybe_unused tegra210_i2s_runtime_suspend(struct device *dev)
{
struct tegra210_i2s *i2s = dev_get_drvdata(dev);
regcache_cache_only(i2s->regmap, true);
regcache_mark_dirty(i2s->regmap);
clk_disable_unprepare(i2s->clk_i2s);
return 0;
}
static int __maybe_unused tegra210_i2s_runtime_resume(struct device