/* * tegra_asoc_utils_alt.c - MCLK and DAP Utility driver * * Author: Stephen Warren * Copyright (c) 2010-2019 NVIDIA CORPORATION. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include #include #include #include #include #include #include #include #include #include #include #include #include "tegra_asoc_utils_alt.h" int tegra_alt_asoc_utils_set_rate(struct tegra_asoc_audio_clock_info *data, int srate, int mclk, int clk_out_rate) { int new_baseclock; int ahub_rate = 0; bool clk_change; int err; switch (srate) { case 11025: case 22050: case 44100: case 88200: case 176400: if (data->soc < TEGRA_ASOC_UTILS_SOC_TEGRA186) new_baseclock = 338688000; else { new_baseclock = data->clk_rates[PLLA_x11025_RATE]; mclk = data->clk_rates[PLLA_OUT0_x11025_RATE]; ahub_rate = data->clk_rates[AHUB_x11025_RATE]; if (srate <= 11025) { /* half the pll_a_out0 to support lower * sampling rate divider */ mclk = mclk >> 1; ahub_rate = ahub_rate >> 1; } clk_out_rate = srate * data->mclk_scale; data->clk_out_rate = clk_out_rate; } break; case 8000: case 16000: case 32000: case 48000: case 64000: case 96000: case 192000: if (data->soc < TEGRA_ASOC_UTILS_SOC_TEGRA186) new_baseclock = 368640000; else { new_baseclock = data->clk_rates[PLLA_x8000_RATE]; mclk = data->clk_rates[PLLA_OUT0_x8000_RATE]; ahub_rate = data->clk_rates[AHUB_x8000_RATE]; if (srate <= 8000) { /* half the pll_a_out0 to support lower * sampling rate divider */ mclk = mclk >> 1; ahub_rate = ahub_rate >> 1; } clk_out_rate = srate * data->mclk_scale; data->clk_out_rate = clk_out_rate; } break; default: return -EINVAL; } clk_change = ((new_baseclock != data->set_baseclock) || (mclk != data->set_mclk) || (clk_out_rate != data->set_clk_out_rate)); if (!clk_change) return 0; /* Don't change rate if already one dai-link is using it */ if (data->lock_count) return -EINVAL; data->set_baseclock = 0; data->set_mclk = 0; err = clk_set_rate(data->clk_pll_a, new_baseclock); 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, mclk); if (err) { dev_err(data->dev, "Can't set clk_pll_a_out0 rate: %d\n", err); return err; } if (data->soc > TEGRA_ASOC_UTILS_SOC_TEGRA210) { err = clk_set_rate(data->clk_ahub, ahub_rate); if (err) { dev_err(data->dev, "Can't set clk_cdev1 rate: %d\n", err); return err; } } err = clk_set_rate(data->clk_cdev1, clk_out_rate); if (err) { dev_err(data->dev, "Can't set clk_cdev1 rate: %d\n", err); return err; } data->set_baseclock = new_baseclock; data->set_mclk = mclk; data->set_clk_out_rate = clk_out_rate; return 0; } EXPORT_SYMBOL_GPL(tegra_alt_asoc_utils_set_rate); void tegra_alt_asoc_utils_lock_clk_rate(struct tegra_asoc_audio_clock_info *data, int lock) { if (lock) data->lock_count++; else if (data->lock_count) data->lock_count--; } EXPORT_SYMBOL_GPL(tegra_alt_asoc_utils_lock_clk_rate); int tegra_alt_asoc_utils_clk_enable(struct tegra_asoc_audio_clock_info *data) { int err; if (data->soc == TEGRA_ASOC_UTILS_SOC_TEGRA186) reset_control_reset(data->clk_cdev1_rst); err = clk_prepare_enable(data->clk_cdev1); if (err) { dev_err(data->dev, "Can't enable cdev1: %d\n", err); return err; } return 0; } EXPORT_SYMBOL_GPL(tegra_alt_asoc_utils_clk_enable); int tegra_alt_asoc_utils_clk_disable(struct tegra_asoc_audio_clock_info *data) { clk_disable_unprepare(data->clk_cdev1); return 0; } EXPORT_SYMBOL_GPL(tegra_alt_asoc_utils_clk_disable); int tegra_alt_asoc_utils_init(struct tegra_asoc_audio_clock_info *data, struct device *dev, struct snd_soc_card *card) { data->dev = dev; data->card = card; data->mclk_scale = 256; if (of_machine_is_compatible("nvidia,tegra210") || of_machine_is_compatible("nvidia,tegra210b01")) 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 /* DT boot, but unknown SoC */ return -EINVAL; data->clk_m = devm_clk_get(dev, "clk_m"); if (IS_ERR(data->clk_m)) { dev_err(data->dev, "Can't retrieve clk clk_m\n"); return PTR_ERR(data->clk_m); } 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, "pll_a_out0"); if (IS_ERR(data->clk_pll_a_out0)) { dev_err(data->dev, "Can't retrieve clk pll_a_out0\n"); return PTR_ERR(data->clk_pll_a_out0); } data->clk_cdev1 = devm_clk_get(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) { data->clk_ahub = devm_clk_get(dev, "ahub"); if (IS_ERR(data->clk_ahub)) { dev_err(data->dev, "Can't retrieve clk ahub\n"); return PTR_ERR(data->clk_ahub); } } if (data->soc == TEGRA_ASOC_UTILS_SOC_TEGRA186) { data->clk_cdev1_rst = devm_reset_control_get(dev, "extern1_rst"); if (IS_ERR(data->clk_cdev1_rst)) { dev_err(dev, "Reset control is not found, err: %ld\n", PTR_ERR(data->clk_cdev1_rst)); return PTR_ERR(data->clk_cdev1_rst); } reset_control_reset(data->clk_cdev1_rst); } return 0; } EXPORT_SYMBOL_GPL(tegra_alt_asoc_utils_init); int tegra_alt_asoc_utils_set_extern_parent( struct tegra_asoc_audio_clock_info *data, const char *parent) { unsigned long rate; int err; rate = clk_get_rate(data->clk_cdev1); if (!strcmp(parent, "clk_m")) { err = clk_set_parent(data->clk_cdev1, data->clk_m); if (err) { dev_err(data->dev, "Can't set clk extern1 parent"); return err; } } else if (!strcmp(parent, "pll_a_out0")) { err = clk_set_parent(data->clk_cdev1, data->clk_pll_a_out0); if (err) { dev_err(data->dev, "Can't set clk cdev1/extern1 parent"); return err; } } err = clk_set_rate(data->clk_cdev1, rate); if (err) { dev_err(data->dev, "Can't set clk rate"); return err; } return 0; } EXPORT_SYMBOL_GPL(tegra_alt_asoc_utils_set_extern_parent); MODULE_AUTHOR("Stephen Warren "); MODULE_DESCRIPTION("Tegra ASoC utility code"); MODULE_LICENSE("GPL");