Linux Audio

Check our new training course

Loading...
Note: File does not exist in v3.1.
  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);