cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

clk-device.c (5224B)


      1// SPDX-License-Identifier: GPL-2.0-only
      2
      3#include <linux/clk.h>
      4#include <linux/clk-provider.h>
      5#include <linux/mutex.h>
      6#include <linux/of_device.h>
      7#include <linux/platform_device.h>
      8#include <linux/pm_domain.h>
      9#include <linux/pm_opp.h>
     10#include <linux/pm_runtime.h>
     11#include <linux/slab.h>
     12
     13#include <soc/tegra/common.h>
     14
     15#include "clk.h"
     16
     17/*
     18 * This driver manages performance state of the core power domain for the
     19 * independent PLLs and system clocks.  We created a virtual clock device
     20 * for such clocks, see tegra_clk_dev_register().
     21 */
     22
     23struct tegra_clk_device {
     24	struct notifier_block clk_nb;
     25	struct device *dev;
     26	struct clk_hw *hw;
     27	struct mutex lock;
     28};
     29
     30static int tegra_clock_set_pd_state(struct tegra_clk_device *clk_dev,
     31				    unsigned long rate)
     32{
     33	struct device *dev = clk_dev->dev;
     34	struct dev_pm_opp *opp;
     35	unsigned int pstate;
     36
     37	opp = dev_pm_opp_find_freq_ceil(dev, &rate);
     38	if (opp == ERR_PTR(-ERANGE)) {
     39		/*
     40		 * Some clocks may be unused by a particular board and they
     41		 * may have uninitiated clock rate that is overly high.  In
     42		 * this case clock is expected to be disabled, but still we
     43		 * need to set up performance state of the power domain and
     44		 * not error out clk initialization.  A typical example is
     45		 * a PCIe clock on Android tablets.
     46		 */
     47		dev_dbg(dev, "failed to find ceil OPP for %luHz\n", rate);
     48		opp = dev_pm_opp_find_freq_floor(dev, &rate);
     49	}
     50
     51	if (IS_ERR(opp)) {
     52		dev_err(dev, "failed to find OPP for %luHz: %pe\n", rate, opp);
     53		return PTR_ERR(opp);
     54	}
     55
     56	pstate = dev_pm_opp_get_required_pstate(opp, 0);
     57	dev_pm_opp_put(opp);
     58
     59	return dev_pm_genpd_set_performance_state(dev, pstate);
     60}
     61
     62static int tegra_clock_change_notify(struct notifier_block *nb,
     63				     unsigned long msg, void *data)
     64{
     65	struct clk_notifier_data *cnd = data;
     66	struct tegra_clk_device *clk_dev;
     67	int err = 0;
     68
     69	clk_dev = container_of(nb, struct tegra_clk_device, clk_nb);
     70
     71	mutex_lock(&clk_dev->lock);
     72	switch (msg) {
     73	case PRE_RATE_CHANGE:
     74		if (cnd->new_rate > cnd->old_rate)
     75			err = tegra_clock_set_pd_state(clk_dev, cnd->new_rate);
     76		break;
     77
     78	case ABORT_RATE_CHANGE:
     79		err = tegra_clock_set_pd_state(clk_dev, cnd->old_rate);
     80		break;
     81
     82	case POST_RATE_CHANGE:
     83		if (cnd->new_rate < cnd->old_rate)
     84			err = tegra_clock_set_pd_state(clk_dev, cnd->new_rate);
     85		break;
     86
     87	default:
     88		break;
     89	}
     90	mutex_unlock(&clk_dev->lock);
     91
     92	return notifier_from_errno(err);
     93}
     94
     95static int tegra_clock_sync_pd_state(struct tegra_clk_device *clk_dev)
     96{
     97	unsigned long rate;
     98	int ret;
     99
    100	mutex_lock(&clk_dev->lock);
    101
    102	rate = clk_hw_get_rate(clk_dev->hw);
    103	ret = tegra_clock_set_pd_state(clk_dev, rate);
    104
    105	mutex_unlock(&clk_dev->lock);
    106
    107	return ret;
    108}
    109
    110static int tegra_clock_probe(struct platform_device *pdev)
    111{
    112	struct tegra_core_opp_params opp_params = {};
    113	struct tegra_clk_device *clk_dev;
    114	struct device *dev = &pdev->dev;
    115	struct clk *clk;
    116	int err;
    117
    118	if (!dev->pm_domain)
    119		return -EINVAL;
    120
    121	clk_dev = devm_kzalloc(dev, sizeof(*clk_dev), GFP_KERNEL);
    122	if (!clk_dev)
    123		return -ENOMEM;
    124
    125	clk = devm_clk_get(dev, NULL);
    126	if (IS_ERR(clk))
    127		return PTR_ERR(clk);
    128
    129	clk_dev->dev = dev;
    130	clk_dev->hw = __clk_get_hw(clk);
    131	clk_dev->clk_nb.notifier_call = tegra_clock_change_notify;
    132	mutex_init(&clk_dev->lock);
    133
    134	platform_set_drvdata(pdev, clk_dev);
    135
    136	/*
    137	 * Runtime PM was already enabled for this device by the parent clk
    138	 * driver and power domain state should be synced under clk_dev lock,
    139	 * hence we don't use the common OPP helper that initializes OPP
    140	 * state. For some clocks common OPP helper may fail to find ceil
    141	 * rate, it's handled by this driver.
    142	 */
    143	err = devm_tegra_core_dev_init_opp_table(dev, &opp_params);
    144	if (err)
    145		return err;
    146
    147	err = clk_notifier_register(clk, &clk_dev->clk_nb);
    148	if (err) {
    149		dev_err(dev, "failed to register clk notifier: %d\n", err);
    150		return err;
    151	}
    152
    153	/*
    154	 * The driver is attaching to a potentially active/resumed clock, hence
    155	 * we need to sync the power domain performance state in a accordance to
    156	 * the clock rate if clock is resumed.
    157	 */
    158	err = tegra_clock_sync_pd_state(clk_dev);
    159	if (err)
    160		goto unreg_clk;
    161
    162	return 0;
    163
    164unreg_clk:
    165	clk_notifier_unregister(clk, &clk_dev->clk_nb);
    166
    167	return err;
    168}
    169
    170/*
    171 * Tegra GENPD driver enables clocks during NOIRQ phase. It can't be done
    172 * for clocks served by this driver because runtime PM is unavailable in
    173 * NOIRQ phase. We will keep clocks resumed during suspend to mitigate this
    174 * problem. In practice this makes no difference from a power management
    175 * perspective since voltage is kept at a nominal level during suspend anyways.
    176 */
    177static const struct dev_pm_ops tegra_clock_pm = {
    178	SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_resume_and_get, pm_runtime_put)
    179};
    180
    181static const struct of_device_id tegra_clock_match[] = {
    182	{ .compatible = "nvidia,tegra20-sclk" },
    183	{ .compatible = "nvidia,tegra30-sclk" },
    184	{ .compatible = "nvidia,tegra30-pllc" },
    185	{ .compatible = "nvidia,tegra30-plle" },
    186	{ .compatible = "nvidia,tegra30-pllm" },
    187	{ }
    188};
    189
    190static struct platform_driver tegra_clock_driver = {
    191	.driver = {
    192		.name = "tegra-clock",
    193		.of_match_table = tegra_clock_match,
    194		.pm = &tegra_clock_pm,
    195		.suppress_bind_attrs = true,
    196	},
    197	.probe = tegra_clock_probe,
    198};
    199builtin_platform_driver(tegra_clock_driver);