123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199 |
- // 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);
|