Files
linux-nv-oot/sound/soc/tegra/tegra_asoc_utils.c
Sameer Pujar 1e35fdd8c5 ASoC: tegra: Align with upstream DT for compatible and clock name
It is nice to have downstream DTB working well with upstream kernel
drivers. This gives a flexibile option for users to choose between
out-of-tree and upstream audio drivers by selectively adding either
of these to deny list.

For clock PLLA_OUT0, upstream DT uses 'plla_out0' clock name where
as downstream uses 'pll_a_out0'. So aligning with upstream DT makes
probe to fail. Thus update the clock name in driver to match upstream
DT bindings.

The sound DT supports compatibles to either work with upstream or
downstream machine drivers. Earlier the source 'tegra_codecs.c' was
modified to make it potentially usable for upstream machine driver
as well and thus it has upstream compatible checks. This check is
now replaced with a flag which is known to be set only during
upstream machine driver probe.

Bug 4119612

Change-Id: Icd5d97343029e35a5702ff4dc527881fb9277346
Signed-off-by: Sameer Pujar <spujar@nvidia.com>
Reviewed-on: https://git-master.nvidia.com/r/c/linux-nv-oot/+/2948525
Reviewed-by: Mohan Kumar D <mkumard@nvidia.com>
Reviewed-by: Dara Ramesh <dramesh@nvidia.com>
GVS: Gerrit_Virtual_Submit <buildbot_gerritrpt@nvidia.com>
2023-08-10 00:33:15 -07:00

450 lines
11 KiB
C

// SPDX-License-Identifier: GPL-2.0-only
/*
* tegra_asoc_utils.c - Harmony machine ASoC driver
*
* Author: Stephen Warren <swarren@nvidia.com>
* Copyright (c) 2010-2023 NVIDIA CORPORATION. All rights reserved.
*/
#include <linux/clk.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include "tegra_asoc_utils.h"
#define MAX(X, Y) ((X > Y) ? (X) : (Y))
/*
* this will be used for platforms from Tegra210 onwards.
* odd rates: sample rates multiple of 11.025kHz
* even_rates: sample rates multiple of 8kHz
*/
enum rate_type {
ODD_RATE,
EVEN_RATE,
NUM_RATE_TYPE,
};
unsigned int tegra210_pll_base_rate[NUM_RATE_TYPE] = {338688000, 368640000};
unsigned int tegra186_pll_stereo_base_rate[NUM_RATE_TYPE] = {270950400, 294912000};
unsigned int tegra239_pll_base_rate[NUM_RATE_TYPE] = {1264435200, 1277952000};
unsigned int default_pll_out_stereo_rate[NUM_RATE_TYPE] = {45158400, 49152000};
int tegra_asoc_utils_set_rate(struct tegra_asoc_utils_data *data, int srate,
int mclk)
{
int new_baseclock;
bool clk_change;
int err;
switch (srate) {
case 11025:
case 22050:
case 44100:
case 88200:
if (data->soc == TEGRA_ASOC_UTILS_SOC_TEGRA20)
new_baseclock = 56448000;
else if (data->soc == TEGRA_ASOC_UTILS_SOC_TEGRA30)
new_baseclock = 564480000;
else
new_baseclock = 282240000;
break;
case 8000:
case 16000:
case 24000:
case 32000:
case 48000:
case 64000:
case 96000:
if (data->soc == TEGRA_ASOC_UTILS_SOC_TEGRA20)
new_baseclock = 73728000;
else if (data->soc == TEGRA_ASOC_UTILS_SOC_TEGRA30)
new_baseclock = 552960000;
else
new_baseclock = 368640000;
break;
default:
return -EINVAL;
}
clk_change = ((new_baseclock != data->set_baseclock) ||
(mclk != data->set_mclk));
if (!clk_change)
return 0;
data->set_baseclock = 0;
data->set_mclk = 0;
clk_disable_unprepare(data->clk_cdev1);
clk_disable_unprepare(data->clk_pll_a_out0);
clk_disable_unprepare(data->clk_pll_a);
err = clk_set_rate(data->clk_pll_a, new_baseclock);
if (err) {
dev_err(data->dev, "Can't set base pll rate: %d\n", err);
return err;
}
err = clk_set_rate(data->clk_pll_a_out0, mclk);
if (err) {
dev_err(data->dev, "Can't set pll_out rate: %d\n", err);
return err;
}
/* Don't set cdev1/extern1 rate; it's locked to pll_out */
err = clk_prepare_enable(data->clk_pll_a);
if (err) {
dev_err(data->dev, "Can't enable pll: %d\n", err);
return err;
}
err = clk_prepare_enable(data->clk_pll_a_out0);
if (err) {
dev_err(data->dev, "Can't enable pll_out: %d\n", err);
return err;
}
err = clk_prepare_enable(data->clk_cdev1);
if (err) {
dev_err(data->dev, "Can't enable clk_cdev1: %d\n", err);
return err;
}
data->set_baseclock = new_baseclock;
data->set_mclk = mclk;
return 0;
}
EXPORT_SYMBOL_GPL(tegra_asoc_utils_set_rate);
int tegra_asoc_utils_set_ac97_rate(struct tegra_asoc_utils_data *data)
{
const int pll_rate = 73728000;
const int ac97_rate = 24576000;
int err;
clk_disable_unprepare(data->clk_cdev1);
clk_disable_unprepare(data->clk_pll_a_out0);
clk_disable_unprepare(data->clk_pll_a);
/*
* AC97 rate is fixed at 24.576MHz and is used for both the host
* controller and the external codec
*/
err = clk_set_rate(data->clk_pll_a, pll_rate);
if (err) {
dev_err(data->dev, "Can't set pll_a rate: %d\n", err);
return err;
}
err = clk_set_rate(data->clk_pll_a_out0, ac97_rate);
if (err) {
dev_err(data->dev, "Can't set pll_a_out0 rate: %d\n", err);
return err;
}
/* Don't set cdev1/extern1 rate; it's locked to pll_a_out0 */
err = clk_prepare_enable(data->clk_pll_a);
if (err) {
dev_err(data->dev, "Can't enable pll_a: %d\n", err);
return err;
}
err = clk_prepare_enable(data->clk_pll_a_out0);
if (err) {
dev_err(data->dev, "Can't enable pll_a_out0: %d\n", err);
return err;
}
err = clk_prepare_enable(data->clk_cdev1);
if (err) {
dev_err(data->dev, "Can't enable cdev1: %d\n", err);
return err;
}
data->set_baseclock = pll_rate;
data->set_mclk = ac97_rate;
return 0;
}
EXPORT_SYMBOL_GPL(tegra_asoc_utils_set_ac97_rate);
static int modify_parent_clk_base_rates(unsigned int *new_pll_base,
unsigned int *pll_out,
unsigned int req_bclk,
struct tegra_asoc_utils_data *data)
{
unsigned int bclk_div, pll_div;
bool pll_out_halved = false;
if (req_bclk == 0)
return 0;
if (req_bclk > *pll_out)
return -EOPNOTSUPP;
if ((*pll_out / req_bclk) > 128) {
/* reduce pll_out rate to support lower sampling rates */
*pll_out >>= 1;
pll_out_halved = true;
}
/* Modify base rates on chips >= T186 if fractional dividier is seen */
if (data->soc >= TEGRA_ASOC_UTILS_SOC_TEGRA186 &&
(*pll_out % req_bclk)) {
/* Below logic is added to reduce dynamic range
* of PLLA (~37MHz). Min and max plla for chips >= t186
* are 258.048 MHz and 294.912 MHz respectively. PLLA dynamic
* range is kept minimal to avoid clk ramp up/down issues
* and avoid halving plla if already halved
*/
if (!pll_out_halved && req_bclk <= (*pll_out >> 1))
*pll_out >>= 1;
*new_pll_base = MAX(data->pll_base_rate[EVEN_RATE],
data->pll_base_rate[ODD_RATE]);
/* Modifying base rates for i2s parent and grand parent
* clocks so that i2s rate can be derived with integer division
* as fractional divider is not supported in HW
*/
bclk_div = *pll_out / req_bclk;
*pll_out = req_bclk * bclk_div;
pll_div = *new_pll_base / *pll_out;
*new_pll_base = pll_div * (*pll_out);
/* TODO: Make sure that the dynamic range is not violated
* by having chip specific lower and upper limits of PLLA
*/
}
return 0;
}
int tegra_asoc_utils_set_tegra210_rate(struct tegra_asoc_utils_data *data,
unsigned int sample_rate,
unsigned int channels,
unsigned int sample_size)
{
unsigned int new_pll_base, pll_out, aud_mclk = 0, req_bclk;
int err;
if (data->fixed_pll)
goto update_mclk_rate;
switch (sample_rate) {
case 11025:
case 22050:
case 44100:
case 88200:
case 176400:
new_pll_base = data->pll_base_rate[ODD_RATE];
pll_out = default_pll_out_stereo_rate[ODD_RATE];
break;
case 8000:
case 16000:
case 24000:
case 32000:
case 48000:
case 64000:
case 96000:
case 192000:
new_pll_base = data->pll_base_rate[EVEN_RATE];
pll_out = default_pll_out_stereo_rate[EVEN_RATE];
break;
default:
return -EINVAL;
}
req_bclk = sample_rate * channels * sample_size;
err = modify_parent_clk_base_rates(&new_pll_base,
&pll_out, req_bclk, data);
if (err) {
dev_err(data->dev, "Clk rate %d not supported\n",
req_bclk);
return err;
}
if (data->set_baseclock != new_pll_base) {
err = clk_set_rate(data->clk_pll_a, new_pll_base);
if (err) {
dev_err(data->dev, "Can't set clk_pll_a rate: %d\n",
err);
return err;
}
data->set_baseclock = new_pll_base;
}
if (data->set_pll_out != pll_out) {
err = clk_set_rate(data->clk_pll_a_out0, pll_out);
if (err) {
dev_err(data->dev, "Can't set clk_pll_a_out0 rate: %d\n",
err);
return err;
}
data->set_pll_out = pll_out;
}
update_mclk_rate:
if (data->mclk_fs)
aud_mclk = sample_rate * data->mclk_fs;
if (data->set_mclk != aud_mclk) {
err = clk_set_rate(data->clk_cdev1, aud_mclk);
if (err) {
dev_err(data->dev, "Can't set clk_cdev1 rate: %d\n",
err);
return err;
}
data->set_mclk = aud_mclk;
}
return 0;
}
EXPORT_SYMBOL_GPL(tegra_asoc_utils_set_tegra210_rate);
int tegra_asoc_utils_clk_enable(struct tegra_asoc_utils_data *data)
{
int err;
err = clk_prepare_enable(data->clk_cdev1);
if (err) {
dev_err(data->dev, "Can't enable clock cdev1\n");
return err;
}
return 0;
}
EXPORT_SYMBOL_GPL(tegra_asoc_utils_clk_enable);
void tegra_asoc_utils_clk_disable(struct tegra_asoc_utils_data *data)
{
clk_disable_unprepare(data->clk_cdev1);
}
EXPORT_SYMBOL_GPL(tegra_asoc_utils_clk_disable);
int tegra_asoc_utils_init(struct tegra_asoc_utils_data *data,
struct device *dev)
{
struct clk *clk_out_1, *clk_extern1;
int ret;
data->dev = dev;
if (of_machine_is_compatible("nvidia,tegra20"))
data->soc = TEGRA_ASOC_UTILS_SOC_TEGRA20;
else if (of_machine_is_compatible("nvidia,tegra30"))
data->soc = TEGRA_ASOC_UTILS_SOC_TEGRA30;
else if (of_machine_is_compatible("nvidia,tegra114"))
data->soc = TEGRA_ASOC_UTILS_SOC_TEGRA114;
else if (of_machine_is_compatible("nvidia,tegra124"))
data->soc = TEGRA_ASOC_UTILS_SOC_TEGRA124;
else if (of_machine_is_compatible("nvidia,tegra210"))
data->soc = TEGRA_ASOC_UTILS_SOC_TEGRA210;
else if (of_machine_is_compatible("nvidia,tegra186"))
data->soc = TEGRA_ASOC_UTILS_SOC_TEGRA186;
else if (of_machine_is_compatible("nvidia,tegra194"))
data->soc = TEGRA_ASOC_UTILS_SOC_TEGRA194;
else if (of_machine_is_compatible("nvidia,tegra234"))
data->soc = TEGRA_ASOC_UTILS_SOC_TEGRA234;
else if (of_machine_is_compatible("nvidia,tegra239"))
data->soc = TEGRA_ASOC_UTILS_SOC_TEGRA239;
else {
dev_err(data->dev, "SoC unknown to Tegra ASoC utils\n");
return -EINVAL;
}
data->clk_pll_a = devm_clk_get(dev, "pll_a");
if (IS_ERR(data->clk_pll_a)) {
dev_err(data->dev, "Can't retrieve clk pll_a\n");
return PTR_ERR(data->clk_pll_a);
}
data->clk_pll_a_out0 = devm_clk_get(dev, "plla_out0");
if (IS_ERR(data->clk_pll_a_out0)) {
dev_err(data->dev, "Can't retrieve clk plla_out0\n");
return PTR_ERR(data->clk_pll_a_out0);
}
/* FIXME: data->clk_cdev1 = devm_clk_get_optional(dev, "mclk"); */
data->clk_cdev1 = devm_clk_get_optional(dev, "extern1");
if (IS_ERR(data->clk_cdev1)) {
dev_err(data->dev, "Can't retrieve clk cdev1\n");
return PTR_ERR(data->clk_cdev1);
}
if (data->soc < TEGRA_ASOC_UTILS_SOC_TEGRA210) {
ret = tegra_asoc_utils_set_rate(data, 44100, 256 * 44100);
if (ret)
return ret;
}
if (data->soc < TEGRA_ASOC_UTILS_SOC_TEGRA186)
data->pll_base_rate = tegra210_pll_base_rate;
else if (data->soc < TEGRA_ASOC_UTILS_SOC_TEGRA239)
data->pll_base_rate = tegra186_pll_stereo_base_rate;
else
data->pll_base_rate = tegra239_pll_base_rate;
/*
* If clock parents are not set in DT, configure here to use clk_out_1
* as mclk and extern1 as parent for Tegra30 and higher.
*/
if (!of_find_property(dev->of_node, "assigned-clock-parents", NULL) &&
data->soc > TEGRA_ASOC_UTILS_SOC_TEGRA20) {
dev_warn(data->dev,
"Configuring clocks for a legacy device-tree\n");
dev_warn(data->dev,
"Please update DT to use assigned-clock-parents\n");
clk_extern1 = devm_clk_get_optional(dev, "extern1");
if (IS_ERR(clk_extern1)) {
dev_err(data->dev, "Can't retrieve clk extern1\n");
return PTR_ERR(clk_extern1);
}
ret = clk_set_parent(clk_extern1, data->clk_pll_a_out0);
if (ret < 0) {
dev_err(data->dev,
"Set parent failed for clk extern1\n");
return ret;
}
clk_out_1 = devm_clk_get(dev, "pmc_clk_out_1");
if (IS_ERR(clk_out_1)) {
dev_err(data->dev, "Can't retrieve pmc_clk_out_1\n");
return PTR_ERR(clk_out_1);
}
ret = clk_set_parent(clk_out_1, clk_extern1);
if (ret < 0) {
dev_err(data->dev,
"Set parent failed for pmc_clk_out_1\n");
return ret;
}
data->clk_cdev1 = clk_out_1;
}
/*
* FIXME: There is some unknown dependency between audio mclk disable
* and suspend-resume functionality on Tegra30, although audio mclk is
* only needed for audio.
*/
ret = clk_prepare_enable(data->clk_cdev1);
if (ret) {
dev_err(data->dev, "Can't enable cdev1: %d\n", ret);
return ret;
}
return 0;
}
EXPORT_SYMBOL_GPL(tegra_asoc_utils_init);
MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>");
MODULE_DESCRIPTION("Tegra ASoC utility code");
MODULE_LICENSE("GPL");