clk: tegra: Changes for v5.17-rc1
This contains a simple fix for the VDE clock on Tegra114 and some preparation work to support runtime PM and generic power domains. -----BEGIN PGP SIGNATURE----- iQJHBAABCAAxFiEEiOrDCAFJzPfAjcif3SOs138+s6EFAmG8r9ITHHRyZWRpbmdA bnZpZGlhLmNvbQAKCRDdI6zXfz6zoTZZD/4sXPJ3KRt+KHi2KzNlw1F1VuzL54cy 8mvxxHnvQzoHAhzciphCbNphn39gQ06nZP9Wmdlj4WmEY7zzpDcKTdM+dQwgoGrM IEqg4B13qNi7gaFpahoN0Ygjp/C6x8R2b18KKrytp4UemLuEH7IPL1utHm7YWzye srUymc20Fs49qbHwOyyyLp7lg9Kpn3oSRSxVSZbpmUK+r465MnXlxgrHtur+UaKF dV7I3UQuNJxLng5E6t8/LgFo2FwPKrYUit8/vhwUiGO1IbfSOlVzKcWn7J1Yfo7y IDRfrYMXu/TYObCzE3qTiMSEhUWz+uhfDonUhFNHXI0qCpx94k0MhjEGhDyZknJG 8alwumFP8NX96N/CC+GWnJhuGf6M32gte6O1Yh9aa6jA4OwsuDXdx8YRu/R3ABYz 7rgfENGarl5UvgcNr8smNc03r8BZrkMl2wkLU6owUI+CY3ToY8R99ncvfuKE7ZFq ATezvxWmjjlcfdSxDjVb7PJvBmLNwPGOv/dTSKXcRcrnAFyqZgnW7zGKXSJdBIbB QYHeo7GpLbZoiJvqUsn7AT85NZyIIPwD5kkRMTg2TlV1To7H/sKzKsVNl2y9j4Nd WHxc2zawuU8a09fz2RQDe7sQTR0Kn9WchYSnITTpunhymqbPa7nc8B56LTuMoKAN 0ywl+4UfbDRvYQ== =aQXh -----END PGP SIGNATURE----- Merge tag 'for-5.17-clk' of git://git.kernel.org/pub/scm/linux/kernel/git/tegra/linux into clk-nvidia Pull Tegra clk driver updates from Thierry Reding: This contains a simple fix for the VDE clock on Tegra114 and some preparation work to support runtime PM and generic power domains. * tag 'for-5.17-clk' of git://git.kernel.org/pub/scm/linux/kernel/git/tegra/linux: clk: tegra: Support runtime PM and power domain clk: tegra: Make vde a child of pll_p on tegra114
This commit is contained in:
commit
fcfc6ea4a4
|
@ -1,6 +1,7 @@
|
|||
# SPDX-License-Identifier: GPL-2.0
|
||||
obj-y += clk.o
|
||||
obj-y += clk-audio-sync.o
|
||||
obj-y += clk-device.o
|
||||
obj-y += clk-dfll.o
|
||||
obj-y += clk-divider.o
|
||||
obj-y += clk-periph.o
|
||||
|
|
|
@ -0,0 +1,199 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/clk-provider.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pm_domain.h>
|
||||
#include <linux/pm_opp.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#include <soc/tegra/common.h>
|
||||
|
||||
#include "clk.h"
|
||||
|
||||
/*
|
||||
* This driver manages performance state of the core power domain for the
|
||||
* independent PLLs and system clocks. We created a virtual clock device
|
||||
* for such clocks, see tegra_clk_dev_register().
|
||||
*/
|
||||
|
||||
struct tegra_clk_device {
|
||||
struct notifier_block clk_nb;
|
||||
struct device *dev;
|
||||
struct clk_hw *hw;
|
||||
struct mutex lock;
|
||||
};
|
||||
|
||||
static int tegra_clock_set_pd_state(struct tegra_clk_device *clk_dev,
|
||||
unsigned long rate)
|
||||
{
|
||||
struct device *dev = clk_dev->dev;
|
||||
struct dev_pm_opp *opp;
|
||||
unsigned int pstate;
|
||||
|
||||
opp = dev_pm_opp_find_freq_ceil(dev, &rate);
|
||||
if (opp == ERR_PTR(-ERANGE)) {
|
||||
/*
|
||||
* Some clocks may be unused by a particular board and they
|
||||
* may have uninitiated clock rate that is overly high. In
|
||||
* this case clock is expected to be disabled, but still we
|
||||
* need to set up performance state of the power domain and
|
||||
* not error out clk initialization. A typical example is
|
||||
* a PCIe clock on Android tablets.
|
||||
*/
|
||||
dev_dbg(dev, "failed to find ceil OPP for %luHz\n", rate);
|
||||
opp = dev_pm_opp_find_freq_floor(dev, &rate);
|
||||
}
|
||||
|
||||
if (IS_ERR(opp)) {
|
||||
dev_err(dev, "failed to find OPP for %luHz: %pe\n", rate, opp);
|
||||
return PTR_ERR(opp);
|
||||
}
|
||||
|
||||
pstate = dev_pm_opp_get_required_pstate(opp, 0);
|
||||
dev_pm_opp_put(opp);
|
||||
|
||||
return dev_pm_genpd_set_performance_state(dev, pstate);
|
||||
}
|
||||
|
||||
static int tegra_clock_change_notify(struct notifier_block *nb,
|
||||
unsigned long msg, void *data)
|
||||
{
|
||||
struct clk_notifier_data *cnd = data;
|
||||
struct tegra_clk_device *clk_dev;
|
||||
int err = 0;
|
||||
|
||||
clk_dev = container_of(nb, struct tegra_clk_device, clk_nb);
|
||||
|
||||
mutex_lock(&clk_dev->lock);
|
||||
switch (msg) {
|
||||
case PRE_RATE_CHANGE:
|
||||
if (cnd->new_rate > cnd->old_rate)
|
||||
err = tegra_clock_set_pd_state(clk_dev, cnd->new_rate);
|
||||
break;
|
||||
|
||||
case ABORT_RATE_CHANGE:
|
||||
err = tegra_clock_set_pd_state(clk_dev, cnd->old_rate);
|
||||
break;
|
||||
|
||||
case POST_RATE_CHANGE:
|
||||
if (cnd->new_rate < cnd->old_rate)
|
||||
err = tegra_clock_set_pd_state(clk_dev, cnd->new_rate);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
mutex_unlock(&clk_dev->lock);
|
||||
|
||||
return notifier_from_errno(err);
|
||||
}
|
||||
|
||||
static int tegra_clock_sync_pd_state(struct tegra_clk_device *clk_dev)
|
||||
{
|
||||
unsigned long rate;
|
||||
int ret;
|
||||
|
||||
mutex_lock(&clk_dev->lock);
|
||||
|
||||
rate = clk_hw_get_rate(clk_dev->hw);
|
||||
ret = tegra_clock_set_pd_state(clk_dev, rate);
|
||||
|
||||
mutex_unlock(&clk_dev->lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int tegra_clock_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct tegra_core_opp_params opp_params = {};
|
||||
struct tegra_clk_device *clk_dev;
|
||||
struct device *dev = &pdev->dev;
|
||||
struct clk *clk;
|
||||
int err;
|
||||
|
||||
if (!dev->pm_domain)
|
||||
return -EINVAL;
|
||||
|
||||
clk_dev = devm_kzalloc(dev, sizeof(*clk_dev), GFP_KERNEL);
|
||||
if (!clk_dev)
|
||||
return -ENOMEM;
|
||||
|
||||
clk = devm_clk_get(dev, NULL);
|
||||
if (IS_ERR(clk))
|
||||
return PTR_ERR(clk);
|
||||
|
||||
clk_dev->dev = dev;
|
||||
clk_dev->hw = __clk_get_hw(clk);
|
||||
clk_dev->clk_nb.notifier_call = tegra_clock_change_notify;
|
||||
mutex_init(&clk_dev->lock);
|
||||
|
||||
platform_set_drvdata(pdev, clk_dev);
|
||||
|
||||
/*
|
||||
* Runtime PM was already enabled for this device by the parent clk
|
||||
* driver and power domain state should be synced under clk_dev lock,
|
||||
* hence we don't use the common OPP helper that initializes OPP
|
||||
* state. For some clocks common OPP helper may fail to find ceil
|
||||
* rate, it's handled by this driver.
|
||||
*/
|
||||
err = devm_tegra_core_dev_init_opp_table(dev, &opp_params);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
err = clk_notifier_register(clk, &clk_dev->clk_nb);
|
||||
if (err) {
|
||||
dev_err(dev, "failed to register clk notifier: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
/*
|
||||
* The driver is attaching to a potentially active/resumed clock, hence
|
||||
* we need to sync the power domain performance state in a accordance to
|
||||
* the clock rate if clock is resumed.
|
||||
*/
|
||||
err = tegra_clock_sync_pd_state(clk_dev);
|
||||
if (err)
|
||||
goto unreg_clk;
|
||||
|
||||
return 0;
|
||||
|
||||
unreg_clk:
|
||||
clk_notifier_unregister(clk, &clk_dev->clk_nb);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
/*
|
||||
* Tegra GENPD driver enables clocks during NOIRQ phase. It can't be done
|
||||
* for clocks served by this driver because runtime PM is unavailable in
|
||||
* NOIRQ phase. We will keep clocks resumed during suspend to mitigate this
|
||||
* problem. In practice this makes no difference from a power management
|
||||
* perspective since voltage is kept at a nominal level during suspend anyways.
|
||||
*/
|
||||
static const struct dev_pm_ops tegra_clock_pm = {
|
||||
SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_resume_and_get, pm_runtime_put)
|
||||
};
|
||||
|
||||
static const struct of_device_id tegra_clock_match[] = {
|
||||
{ .compatible = "nvidia,tegra20-sclk" },
|
||||
{ .compatible = "nvidia,tegra30-sclk" },
|
||||
{ .compatible = "nvidia,tegra30-pllc" },
|
||||
{ .compatible = "nvidia,tegra30-plle" },
|
||||
{ .compatible = "nvidia,tegra30-pllm" },
|
||||
{ }
|
||||
};
|
||||
|
||||
static struct platform_driver tegra_clock_driver = {
|
||||
.driver = {
|
||||
.name = "tegra-clock",
|
||||
.of_match_table = tegra_clock_match,
|
||||
.pm = &tegra_clock_pm,
|
||||
.suppress_bind_attrs = true,
|
||||
},
|
||||
.probe = tegra_clock_probe,
|
||||
};
|
||||
builtin_platform_driver(tegra_clock_driver);
|
|
@ -1914,7 +1914,7 @@ static struct clk *_tegra_clk_register_pll(struct tegra_clk_pll *pll,
|
|||
/* Data in .init is copied by clk_register(), so stack variable OK */
|
||||
pll->hw.init = &init;
|
||||
|
||||
return clk_register(NULL, &pll->hw);
|
||||
return tegra_clk_dev_register(&pll->hw);
|
||||
}
|
||||
|
||||
struct clk *tegra_clk_register_pll(const char *name, const char *parent_name,
|
||||
|
|
|
@ -226,7 +226,7 @@ struct clk *tegra_clk_register_super_mux(const char *name,
|
|||
/* Data in .init is copied by clk_register(), so stack variable OK */
|
||||
super->hw.init = &init;
|
||||
|
||||
clk = clk_register(NULL, &super->hw);
|
||||
clk = tegra_clk_dev_register(&super->hw);
|
||||
if (IS_ERR(clk))
|
||||
kfree(super);
|
||||
|
||||
|
|
|
@ -1158,7 +1158,7 @@ static struct tegra_clk_init_table init_table[] __initdata = {
|
|||
{ TEGRA114_CLK_XUSB_HS_SRC, TEGRA114_CLK_XUSB_SS_DIV2, 61200000, 0 },
|
||||
{ TEGRA114_CLK_XUSB_FALCON_SRC, TEGRA114_CLK_PLL_P, 204000000, 0 },
|
||||
{ TEGRA114_CLK_XUSB_HOST_SRC, TEGRA114_CLK_PLL_P, 102000000, 0 },
|
||||
{ TEGRA114_CLK_VDE, TEGRA114_CLK_CLK_MAX, 600000000, 0 },
|
||||
{ TEGRA114_CLK_VDE, TEGRA114_CLK_PLL_P, 408000000, 0 },
|
||||
{ TEGRA114_CLK_SPDIF_IN_SYNC, TEGRA114_CLK_CLK_MAX, 24000000, 0 },
|
||||
{ TEGRA114_CLK_I2S0_SYNC, TEGRA114_CLK_CLK_MAX, 24000000, 0 },
|
||||
{ TEGRA114_CLK_I2S1_SYNC, TEGRA114_CLK_CLK_MAX, 24000000, 0 },
|
||||
|
|
|
@ -6,8 +6,11 @@
|
|||
#include <linux/io.h>
|
||||
#include <linux/clk-provider.h>
|
||||
#include <linux/clkdev.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/clk/tegra.h>
|
||||
#include <linux/delay.h>
|
||||
#include <dt-bindings/clock/tegra20-car.h>
|
||||
|
@ -414,7 +417,7 @@ static struct tegra_clk_pll_params pll_e_params = {
|
|||
.fixed_rate = 100000000,
|
||||
};
|
||||
|
||||
static struct tegra_devclk devclks[] __initdata = {
|
||||
static struct tegra_devclk devclks[] = {
|
||||
{ .con_id = "pll_c", .dt_id = TEGRA20_CLK_PLL_C },
|
||||
{ .con_id = "pll_c_out1", .dt_id = TEGRA20_CLK_PLL_C_OUT1 },
|
||||
{ .con_id = "pll_p", .dt_id = TEGRA20_CLK_PLL_P },
|
||||
|
@ -710,13 +713,6 @@ static void tegra20_super_clk_init(void)
|
|||
NULL);
|
||||
clks[TEGRA20_CLK_CCLK] = clk;
|
||||
|
||||
/* SCLK */
|
||||
clk = tegra_clk_register_super_mux("sclk", sclk_parents,
|
||||
ARRAY_SIZE(sclk_parents),
|
||||
CLK_SET_RATE_PARENT | CLK_IS_CRITICAL,
|
||||
clk_base + SCLK_BURST_POLICY, 0, 4, 0, 0, NULL);
|
||||
clks[TEGRA20_CLK_SCLK] = clk;
|
||||
|
||||
/* twd */
|
||||
clk = clk_register_fixed_factor(NULL, "twd", "cclk", 0, 1, 4);
|
||||
clks[TEGRA20_CLK_TWD] = clk;
|
||||
|
@ -1014,7 +1010,7 @@ static struct tegra_cpu_car_ops tegra20_cpu_car_ops = {
|
|||
#endif
|
||||
};
|
||||
|
||||
static struct tegra_clk_init_table init_table[] __initdata = {
|
||||
static struct tegra_clk_init_table init_table[] = {
|
||||
{ TEGRA20_CLK_PLL_P, TEGRA20_CLK_CLK_MAX, 216000000, 1 },
|
||||
{ TEGRA20_CLK_PLL_P_OUT1, TEGRA20_CLK_CLK_MAX, 28800000, 1 },
|
||||
{ TEGRA20_CLK_PLL_P_OUT2, TEGRA20_CLK_CLK_MAX, 48000000, 1 },
|
||||
|
@ -1052,11 +1048,6 @@ static struct tegra_clk_init_table init_table[] __initdata = {
|
|||
{ TEGRA20_CLK_CLK_MAX, TEGRA20_CLK_CLK_MAX, 0, 0 },
|
||||
};
|
||||
|
||||
static void __init tegra20_clock_apply_init_table(void)
|
||||
{
|
||||
tegra_init_from_table(init_table, clks, TEGRA20_CLK_CLK_MAX);
|
||||
}
|
||||
|
||||
/*
|
||||
* Some clocks may be used by different drivers depending on the board
|
||||
* configuration. List those here to register them twice in the clock lookup
|
||||
|
@ -1076,6 +1067,8 @@ static const struct of_device_id pmc_match[] __initconst = {
|
|||
{ },
|
||||
};
|
||||
|
||||
static bool tegra20_car_initialized;
|
||||
|
||||
static struct clk *tegra20_clk_src_onecell_get(struct of_phandle_args *clkspec,
|
||||
void *data)
|
||||
{
|
||||
|
@ -1083,6 +1076,16 @@ static struct clk *tegra20_clk_src_onecell_get(struct of_phandle_args *clkspec,
|
|||
struct clk_hw *hw;
|
||||
struct clk *clk;
|
||||
|
||||
/*
|
||||
* Timer clocks are needed early, the rest of the clocks shouldn't be
|
||||
* available to device drivers until clock tree is fully initialized.
|
||||
*/
|
||||
if (clkspec->args[0] != TEGRA20_CLK_RTC &&
|
||||
clkspec->args[0] != TEGRA20_CLK_TWD &&
|
||||
clkspec->args[0] != TEGRA20_CLK_TIMER &&
|
||||
!tegra20_car_initialized)
|
||||
return ERR_PTR(-EPROBE_DEFER);
|
||||
|
||||
clk = of_clk_src_onecell_get(clkspec, data);
|
||||
if (IS_ERR(clk))
|
||||
return clk;
|
||||
|
@ -1149,10 +1152,48 @@ static void __init tegra20_clock_init(struct device_node *np)
|
|||
tegra_init_dup_clks(tegra_clk_duplicates, clks, TEGRA20_CLK_CLK_MAX);
|
||||
|
||||
tegra_add_of_provider(np, tegra20_clk_src_onecell_get);
|
||||
tegra_register_devclks(devclks, ARRAY_SIZE(devclks));
|
||||
|
||||
tegra_clk_apply_init_table = tegra20_clock_apply_init_table;
|
||||
|
||||
tegra_cpu_car_ops = &tegra20_cpu_car_ops;
|
||||
}
|
||||
CLK_OF_DECLARE(tegra20, "nvidia,tegra20-car", tegra20_clock_init);
|
||||
CLK_OF_DECLARE_DRIVER(tegra20, "nvidia,tegra20-car", tegra20_clock_init);
|
||||
|
||||
/*
|
||||
* Clocks that use runtime PM can't be created at the tegra20_clock_init
|
||||
* time because drivers' base isn't initialized yet, and thus platform
|
||||
* devices can't be created for the clocks. Hence we need to split the
|
||||
* registration of the clocks into two phases. The first phase registers
|
||||
* essential clocks which don't require RPM and are actually used during
|
||||
* early boot. The second phase registers clocks which use RPM and this
|
||||
* is done when device drivers' core API is ready.
|
||||
*/
|
||||
static int tegra20_car_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct clk *clk;
|
||||
|
||||
clk = tegra_clk_register_super_mux("sclk", sclk_parents,
|
||||
ARRAY_SIZE(sclk_parents),
|
||||
CLK_SET_RATE_PARENT | CLK_IS_CRITICAL,
|
||||
clk_base + SCLK_BURST_POLICY, 0, 4, 0, 0, NULL);
|
||||
clks[TEGRA20_CLK_SCLK] = clk;
|
||||
|
||||
tegra_register_devclks(devclks, ARRAY_SIZE(devclks));
|
||||
tegra_init_from_table(init_table, clks, TEGRA20_CLK_CLK_MAX);
|
||||
tegra20_car_initialized = true;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id tegra20_car_match[] = {
|
||||
{ .compatible = "nvidia,tegra20-car" },
|
||||
{ }
|
||||
};
|
||||
|
||||
static struct platform_driver tegra20_car_driver = {
|
||||
.driver = {
|
||||
.name = "tegra20-car",
|
||||
.of_match_table = tegra20_car_match,
|
||||
.suppress_bind_attrs = true,
|
||||
},
|
||||
.probe = tegra20_car_probe,
|
||||
};
|
||||
builtin_platform_driver(tegra20_car_driver);
|
||||
|
|
|
@ -7,8 +7,11 @@
|
|||
#include <linux/delay.h>
|
||||
#include <linux/clk-provider.h>
|
||||
#include <linux/clkdev.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/clk/tegra.h>
|
||||
|
||||
#include <soc/tegra/pmc.h>
|
||||
|
@ -532,7 +535,7 @@ static unsigned long tegra30_input_freq[] = {
|
|||
[12] = 26000000,
|
||||
};
|
||||
|
||||
static struct tegra_devclk devclks[] __initdata = {
|
||||
static struct tegra_devclk devclks[] = {
|
||||
{ .con_id = "pll_c", .dt_id = TEGRA30_CLK_PLL_C },
|
||||
{ .con_id = "pll_c_out1", .dt_id = TEGRA30_CLK_PLL_C_OUT1 },
|
||||
{ .con_id = "pll_p", .dt_id = TEGRA30_CLK_PLL_P },
|
||||
|
@ -812,11 +815,6 @@ static void __init tegra30_pll_init(void)
|
|||
{
|
||||
struct clk *clk;
|
||||
|
||||
/* PLLC */
|
||||
clk = tegra_clk_register_pll("pll_c", "pll_ref", clk_base, pmc_base, 0,
|
||||
&pll_c_params, NULL);
|
||||
clks[TEGRA30_CLK_PLL_C] = clk;
|
||||
|
||||
/* PLLC_OUT1 */
|
||||
clk = tegra_clk_register_divider("pll_c_out1_div", "pll_c",
|
||||
clk_base + PLLC_OUT, 0, TEGRA_DIVIDER_ROUND_UP,
|
||||
|
@ -826,11 +824,6 @@ static void __init tegra30_pll_init(void)
|
|||
0, NULL);
|
||||
clks[TEGRA30_CLK_PLL_C_OUT1] = clk;
|
||||
|
||||
/* PLLM */
|
||||
clk = tegra_clk_register_pll("pll_m", "pll_ref", clk_base, pmc_base,
|
||||
CLK_SET_RATE_GATE, &pll_m_params, NULL);
|
||||
clks[TEGRA30_CLK_PLL_M] = clk;
|
||||
|
||||
/* PLLM_OUT1 */
|
||||
clk = tegra_clk_register_divider("pll_m_out1_div", "pll_m",
|
||||
clk_base + PLLM_OUT, 0, TEGRA_DIVIDER_ROUND_UP,
|
||||
|
@ -880,9 +873,6 @@ static void __init tegra30_pll_init(void)
|
|||
ARRAY_SIZE(pll_e_parents),
|
||||
CLK_SET_RATE_NO_REPARENT,
|
||||
clk_base + PLLE_AUX, 2, 1, 0, NULL);
|
||||
clk = tegra_clk_register_plle("pll_e", "pll_e_mux", clk_base, pmc_base,
|
||||
CLK_GET_RATE_NOCACHE, &pll_e_params, NULL);
|
||||
clks[TEGRA30_CLK_PLL_E] = clk;
|
||||
}
|
||||
|
||||
static const char *cclk_g_parents[] = { "clk_m", "pll_c", "clk_32k", "pll_m",
|
||||
|
@ -971,14 +961,6 @@ static void __init tegra30_super_clk_init(void)
|
|||
NULL);
|
||||
clks[TEGRA30_CLK_CCLK_LP] = clk;
|
||||
|
||||
/* SCLK */
|
||||
clk = tegra_clk_register_super_mux("sclk", sclk_parents,
|
||||
ARRAY_SIZE(sclk_parents),
|
||||
CLK_SET_RATE_PARENT | CLK_IS_CRITICAL,
|
||||
clk_base + SCLK_BURST_POLICY,
|
||||
0, 4, 0, 0, NULL);
|
||||
clks[TEGRA30_CLK_SCLK] = clk;
|
||||
|
||||
/* twd */
|
||||
clk = clk_register_fixed_factor(NULL, "twd", "cclk_g",
|
||||
CLK_SET_RATE_PARENT, 1, 2);
|
||||
|
@ -1214,7 +1196,7 @@ static struct tegra_cpu_car_ops tegra30_cpu_car_ops = {
|
|||
#endif
|
||||
};
|
||||
|
||||
static struct tegra_clk_init_table init_table[] __initdata = {
|
||||
static struct tegra_clk_init_table init_table[] = {
|
||||
{ TEGRA30_CLK_UARTA, TEGRA30_CLK_PLL_P, 408000000, 0 },
|
||||
{ TEGRA30_CLK_UARTB, TEGRA30_CLK_PLL_P, 408000000, 0 },
|
||||
{ TEGRA30_CLK_UARTC, TEGRA30_CLK_PLL_P, 408000000, 0 },
|
||||
|
@ -1259,11 +1241,6 @@ static struct tegra_clk_init_table init_table[] __initdata = {
|
|||
{ TEGRA30_CLK_CLK_MAX, TEGRA30_CLK_CLK_MAX, 0, 0 },
|
||||
};
|
||||
|
||||
static void __init tegra30_clock_apply_init_table(void)
|
||||
{
|
||||
tegra_init_from_table(init_table, clks, TEGRA30_CLK_CLK_MAX);
|
||||
}
|
||||
|
||||
/*
|
||||
* Some clocks may be used by different drivers depending on the board
|
||||
* configuration. List those here to register them twice in the clock lookup
|
||||
|
@ -1294,12 +1271,24 @@ static struct tegra_audio_clk_info tegra30_audio_plls[] = {
|
|||
{ "pll_a", &pll_a_params, tegra_clk_pll_a, "pll_p_out1" },
|
||||
};
|
||||
|
||||
static bool tegra30_car_initialized;
|
||||
|
||||
static struct clk *tegra30_clk_src_onecell_get(struct of_phandle_args *clkspec,
|
||||
void *data)
|
||||
{
|
||||
struct clk_hw *hw;
|
||||
struct clk *clk;
|
||||
|
||||
/*
|
||||
* Timer clocks are needed early, the rest of the clocks shouldn't be
|
||||
* available to device drivers until clock tree is fully initialized.
|
||||
*/
|
||||
if (clkspec->args[0] != TEGRA30_CLK_RTC &&
|
||||
clkspec->args[0] != TEGRA30_CLK_TWD &&
|
||||
clkspec->args[0] != TEGRA30_CLK_TIMER &&
|
||||
!tegra30_car_initialized)
|
||||
return ERR_PTR(-EPROBE_DEFER);
|
||||
|
||||
clk = of_clk_src_onecell_get(clkspec, data);
|
||||
if (IS_ERR(clk))
|
||||
return clk;
|
||||
|
@ -1357,10 +1346,75 @@ static void __init tegra30_clock_init(struct device_node *np)
|
|||
tegra_init_dup_clks(tegra_clk_duplicates, clks, TEGRA30_CLK_CLK_MAX);
|
||||
|
||||
tegra_add_of_provider(np, tegra30_clk_src_onecell_get);
|
||||
tegra_register_devclks(devclks, ARRAY_SIZE(devclks));
|
||||
|
||||
tegra_clk_apply_init_table = tegra30_clock_apply_init_table;
|
||||
|
||||
tegra_cpu_car_ops = &tegra30_cpu_car_ops;
|
||||
}
|
||||
CLK_OF_DECLARE(tegra30, "nvidia,tegra30-car", tegra30_clock_init);
|
||||
CLK_OF_DECLARE_DRIVER(tegra30, "nvidia,tegra30-car", tegra30_clock_init);
|
||||
|
||||
/*
|
||||
* Clocks that use runtime PM can't be created at the tegra30_clock_init
|
||||
* time because drivers' base isn't initialized yet, and thus platform
|
||||
* devices can't be created for the clocks. Hence we need to split the
|
||||
* registration of the clocks into two phases. The first phase registers
|
||||
* essential clocks which don't require RPM and are actually used during
|
||||
* early boot. The second phase registers clocks which use RPM and this
|
||||
* is done when device drivers' core API is ready.
|
||||
*/
|
||||
static int tegra30_car_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct clk *clk;
|
||||
|
||||
/* PLLC */
|
||||
clk = tegra_clk_register_pll("pll_c", "pll_ref", clk_base, pmc_base, 0,
|
||||
&pll_c_params, NULL);
|
||||
clks[TEGRA30_CLK_PLL_C] = clk;
|
||||
|
||||
/* PLLE */
|
||||
clk = tegra_clk_register_plle("pll_e", "pll_e_mux", clk_base, pmc_base,
|
||||
CLK_GET_RATE_NOCACHE, &pll_e_params, NULL);
|
||||
clks[TEGRA30_CLK_PLL_E] = clk;
|
||||
|
||||
/* PLLM */
|
||||
clk = tegra_clk_register_pll("pll_m", "pll_ref", clk_base, pmc_base,
|
||||
CLK_SET_RATE_GATE, &pll_m_params, NULL);
|
||||
clks[TEGRA30_CLK_PLL_M] = clk;
|
||||
|
||||
/* SCLK */
|
||||
clk = tegra_clk_register_super_mux("sclk", sclk_parents,
|
||||
ARRAY_SIZE(sclk_parents),
|
||||
CLK_SET_RATE_PARENT | CLK_IS_CRITICAL,
|
||||
clk_base + SCLK_BURST_POLICY,
|
||||
0, 4, 0, 0, NULL);
|
||||
clks[TEGRA30_CLK_SCLK] = clk;
|
||||
|
||||
tegra_register_devclks(devclks, ARRAY_SIZE(devclks));
|
||||
tegra_init_from_table(init_table, clks, TEGRA30_CLK_CLK_MAX);
|
||||
tegra30_car_initialized = true;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id tegra30_car_match[] = {
|
||||
{ .compatible = "nvidia,tegra30-car" },
|
||||
{ }
|
||||
};
|
||||
|
||||
static struct platform_driver tegra30_car_driver = {
|
||||
.driver = {
|
||||
.name = "tegra30-car",
|
||||
.of_match_table = tegra30_car_match,
|
||||
.suppress_bind_attrs = true,
|
||||
},
|
||||
.probe = tegra30_car_probe,
|
||||
};
|
||||
|
||||
/*
|
||||
* Clock driver must be registered before memory controller driver,
|
||||
* which doesn't support deferred probing for today and is registered
|
||||
* from arch init-level.
|
||||
*/
|
||||
static int tegra30_car_init(void)
|
||||
{
|
||||
return platform_driver_register(&tegra30_car_driver);
|
||||
}
|
||||
postcore_initcall(tegra30_car_init);
|
||||
|
|
|
@ -9,14 +9,19 @@
|
|||
#include <linux/delay.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/clk/tegra.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/reset-controller.h>
|
||||
#include <linux/string.h>
|
||||
|
||||
#include <soc/tegra/fuse.h>
|
||||
|
||||
#include "clk.h"
|
||||
|
||||
/* Global data of Tegra CPU CAR ops */
|
||||
static struct device_node *tegra_car_np;
|
||||
static struct tegra_cpu_car_ops dummy_car_ops;
|
||||
struct tegra_cpu_car_ops *tegra_cpu_car_ops = &dummy_car_ops;
|
||||
|
||||
|
@ -261,8 +266,8 @@ void __init tegra_init_dup_clks(struct tegra_clk_duplicate *dup_list,
|
|||
}
|
||||
}
|
||||
|
||||
void __init tegra_init_from_table(struct tegra_clk_init_table *tbl,
|
||||
struct clk *clks[], int clk_max)
|
||||
void tegra_init_from_table(struct tegra_clk_init_table *tbl,
|
||||
struct clk *clks[], int clk_max)
|
||||
{
|
||||
struct clk *clk;
|
||||
|
||||
|
@ -320,6 +325,8 @@ void __init tegra_add_of_provider(struct device_node *np,
|
|||
{
|
||||
int i;
|
||||
|
||||
tegra_car_np = np;
|
||||
|
||||
for (i = 0; i < clk_num; i++) {
|
||||
if (IS_ERR(clks[i])) {
|
||||
pr_err
|
||||
|
@ -348,7 +355,7 @@ void __init tegra_init_special_resets(unsigned int num,
|
|||
special_reset_deassert = deassert;
|
||||
}
|
||||
|
||||
void __init tegra_register_devclks(struct tegra_devclk *dev_clks, int num)
|
||||
void tegra_register_devclks(struct tegra_devclk *dev_clks, int num)
|
||||
{
|
||||
int i;
|
||||
|
||||
|
@ -372,6 +379,68 @@ struct clk ** __init tegra_lookup_dt_id(int clk_id,
|
|||
return NULL;
|
||||
}
|
||||
|
||||
static struct device_node *tegra_clk_get_of_node(struct clk_hw *hw)
|
||||
{
|
||||
struct device_node *np;
|
||||
char *node_name;
|
||||
|
||||
node_name = kstrdup(hw->init->name, GFP_KERNEL);
|
||||
if (!node_name)
|
||||
return NULL;
|
||||
|
||||
strreplace(node_name, '_', '-');
|
||||
|
||||
for_each_child_of_node(tegra_car_np, np) {
|
||||
if (!strcmp(np->name, node_name))
|
||||
break;
|
||||
}
|
||||
|
||||
kfree(node_name);
|
||||
|
||||
return np;
|
||||
}
|
||||
|
||||
struct clk *tegra_clk_dev_register(struct clk_hw *hw)
|
||||
{
|
||||
struct platform_device *pdev, *parent;
|
||||
const char *dev_name = NULL;
|
||||
struct device *dev = NULL;
|
||||
struct device_node *np;
|
||||
|
||||
np = tegra_clk_get_of_node(hw);
|
||||
|
||||
if (!of_device_is_available(np))
|
||||
goto put_node;
|
||||
|
||||
dev_name = kasprintf(GFP_KERNEL, "tegra_clk_%s", hw->init->name);
|
||||
if (!dev_name)
|
||||
goto put_node;
|
||||
|
||||
parent = of_find_device_by_node(tegra_car_np);
|
||||
if (parent) {
|
||||
pdev = of_platform_device_create(np, dev_name, &parent->dev);
|
||||
put_device(&parent->dev);
|
||||
|
||||
if (!pdev) {
|
||||
pr_err("%s: failed to create device for %pOF\n",
|
||||
__func__, np);
|
||||
goto free_name;
|
||||
}
|
||||
|
||||
dev = &pdev->dev;
|
||||
pm_runtime_enable(dev);
|
||||
} else {
|
||||
WARN(1, "failed to find device for %pOF\n", tegra_car_np);
|
||||
}
|
||||
|
||||
free_name:
|
||||
kfree(dev_name);
|
||||
put_node:
|
||||
of_node_put(np);
|
||||
|
||||
return clk_register(dev, hw);
|
||||
}
|
||||
|
||||
tegra_clk_apply_init_table_func tegra_clk_apply_init_table;
|
||||
|
||||
static int __init tegra_clocks_apply_init_table(void)
|
||||
|
|
|
@ -927,4 +927,6 @@ struct clk *tegra20_clk_register_emc(void __iomem *ioaddr, bool low_jitter);
|
|||
struct clk *tegra210_clk_register_emc(struct device_node *np,
|
||||
void __iomem *regs);
|
||||
|
||||
struct clk *tegra_clk_dev_register(struct clk_hw *hw);
|
||||
|
||||
#endif /* TEGRA_CLK_H */
|
||||
|
|
Loading…
Reference in New Issue