Skip to content

Commit

Permalink
Merge tag 'for-5.17-clk' of git://git.kernel.org/pub/scm/linux/kernel…
Browse files Browse the repository at this point in the history
…/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
  • Loading branch information
Stephen Boyd committed Dec 29, 2021
2 parents fa55b7d + b1bc04a commit fcfc6ea
Show file tree
Hide file tree
Showing 9 changed files with 421 additions and 55 deletions.
1 change: 1 addition & 0 deletions drivers/clk/tegra/Makefile
Original file line number Diff line number Diff line change
@@ -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
Expand Down
199 changes: 199 additions & 0 deletions drivers/clk/tegra/clk-device.c
Original file line number Diff line number Diff line change
@@ -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);
2 changes: 1 addition & 1 deletion drivers/clk/tegra/clk-pll.c
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion drivers/clk/tegra/clk-super.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
2 changes: 1 addition & 1 deletion drivers/clk/tegra/clk-tegra114.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
Expand Down
77 changes: 59 additions & 18 deletions drivers/clk/tegra/clk-tegra20.c
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down Expand Up @@ -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 },
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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 },
Expand Down Expand Up @@ -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
Expand All @@ -1076,13 +1067,25 @@ 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)
{
struct clk_hw *parent_hw;
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;
Expand Down Expand Up @@ -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);
Loading

0 comments on commit fcfc6ea

Please sign in to comment.