Skip to content

Commit

Permalink
soc/tegra: Add devm_tegra_core_dev_init_opp_table()
Browse files Browse the repository at this point in the history
Add common helper which initializes OPP table for Tegra SoC core devices.

Tested-by: Peter Geis <pgwipeout@gmail.com> # Ouya T30
Tested-by: Dmitry Osipenko <digetx@gmail.com> # A500 T20 and Nexus7 T30
Tested-by: Nicolas Chauvet <kwizart@gmail.com> # PAZ00 T20 and TK1 T124
Tested-by: Matt Merhar <mattmerhar@protonmail.com> # Ouya T30
[tested on some other non-upstreamed-yet T20/30/114 devices as well]
Signed-off-by: Dmitry Osipenko <digetx@gmail.com>
  • Loading branch information
digetx authored and intel-lab-lkp committed Jan 21, 2021
1 parent 818d952 commit d506fc2
Show file tree
Hide file tree
Showing 2 changed files with 167 additions and 0 deletions.
137 changes: 137 additions & 0 deletions drivers/soc/tegra/common.c
Expand Up @@ -3,9 +3,16 @@
* Copyright (C) 2014 NVIDIA CORPORATION. All rights reserved.
*/

#define dev_fmt(fmt) "tegra-soc: " fmt

#include <linux/clk.h>
#include <linux/device.h>
#include <linux/export.h>
#include <linux/of.h>
#include <linux/pm_opp.h>

#include <soc/tegra/common.h>
#include <soc/tegra/fuse.h>

static const struct of_device_id tegra_machine_match[] = {
{ .compatible = "nvidia,tegra20", },
Expand All @@ -31,3 +38,133 @@ bool soc_is_tegra(void)

return match != NULL;
}

static int tegra_core_dev_init_opp_state(struct device *dev)
{
struct dev_pm_opp *opp;
unsigned long rate;
struct clk *clk;
int err;

clk = devm_clk_get(dev, NULL);
if (IS_ERR(clk)) {
dev_err(dev, "failed to get clk: %pe\n", clk);
return PTR_ERR(clk);
}

/*
* If voltage regulator presents, then we could select the fastest
* clock rate, but driver doesn't support power management and
* frequency scaling yet, hence the top freq OPP will vote for a
* very high voltage that will produce lot's of heat. Let's select
* OPP for the current/default rate for now.
*
* Clock rate should be pre-initialized (i.e. it's non-zero) either
* by clock driver or by assigned clocks in a device-tree.
*/
rate = clk_get_rate(clk);
if (!rate) {
dev_err(dev, "failed to get clk rate\n");
return -EINVAL;
}

/* find suitable OPP for the clock rate and supportable by hardware */
opp = dev_pm_opp_find_freq_ceil(dev, &rate);

/*
* dev_pm_opp_set_rate() doesn't search for a floor clock rate and it
* will error out if default clock rate is too high, i.e. unsupported
* by a SoC hardware version. Hence will find floor rate by ourselves.
*/
if (opp == ERR_PTR(-ERANGE))
opp = dev_pm_opp_find_freq_floor(dev, &rate);

err = PTR_ERR_OR_ZERO(opp);
if (err) {
dev_err(dev, "failed to get OPP for %ld Hz: %d\n",
rate, err);
return err;
}

dev_pm_opp_put(opp);

/*
* First dummy rate-set initializes voltage vote by setting voltage
* in accordance to the clock rate. We need to do this because some
* drivers currently don't support power management and clock is
* permanently enabled.
*/
err = dev_pm_opp_set_rate(dev, rate);
if (err) {
dev_err(dev, "failed to initialize OPP clock: %d\n", err);
return err;
}

return 0;
}

/**
* devm_tegra_core_dev_init_opp_table() - initialize OPP table
* @cfg: pointer to the OPP table configuration
*
* This function will initialize OPP table and sync OPP state of a Tegra SoC
* core device.
*
* Return: 0 on success or errorno.
*/
int devm_tegra_core_dev_init_opp_table(struct device *dev,
struct tegra_core_opp_params *params)
{
struct opp_table *opp_table;
u32 hw_version;
int err;

opp_table = devm_pm_opp_set_clkname(dev, NULL);
if (IS_ERR(opp_table)) {
dev_err(dev, "failed to set OPP clk %pe\n", opp_table);
return PTR_ERR(opp_table);
}

/* Tegra114+ doesn't support OPP yet */
if (!of_machine_is_compatible("nvidia,tegra20") &&
!of_machine_is_compatible("nvidia,tegra30"))
return -ENODEV;

if (of_machine_is_compatible("nvidia,tegra20"))
hw_version = BIT(tegra_sku_info.soc_process_id);
else
hw_version = BIT(tegra_sku_info.soc_speedo_id);

opp_table = devm_pm_opp_set_supported_hw(dev, &hw_version, 1);
if (IS_ERR(opp_table)) {
dev_err(dev, "failed to set OPP supported HW: %pe\n", opp_table);
return PTR_ERR(opp_table);
}

/*
* Older device-trees have an empty OPP table, hence we will get
* -ENODEV from devm_pm_opp_of_add_table() for the older DTBs.
*
* The OPP table presence also varies per-device and depending
* on a SoC generation, hence -ENODEV is expected to happen for
* the newer DTs as well.
*/
err = devm_pm_opp_of_add_table(dev);
if (err) {
if (err == -ENODEV)
dev_err_once(dev, "OPP table not found, please update device-tree\n");
else
dev_err(dev, "failed to add OPP table: %d\n", err);

return err;
}

if (params->init_state) {
err = tegra_core_dev_init_opp_state(dev);
if (err)
return err;
}

return 0;
}
EXPORT_SYMBOL_GPL(devm_tegra_core_dev_init_opp_table);
30 changes: 30 additions & 0 deletions include/soc/tegra/common.h
Expand Up @@ -6,6 +6,36 @@
#ifndef __SOC_TEGRA_COMMON_H__
#define __SOC_TEGRA_COMMON_H__

#include <linux/errno.h>
#include <linux/types.h>

struct device;

/**
* Tegra SoC core device OPP table configuration
*
* @init_state: pre-initialize OPP state of a device
*/
struct tegra_core_opp_params {
bool init_state;
};

#ifdef CONFIG_ARCH_TEGRA
bool soc_is_tegra(void);
int devm_tegra_core_dev_init_opp_table(struct device *dev,
struct tegra_core_opp_params *cfg);
#else
static inline bool soc_is_tegra(void)
{
return false;
}

static inline int
devm_tegra_core_dev_init_opp_table(struct device *dev,
struct tegra_core_opp_params *cfg)
{
return -ENODEV;
}
#endif

#endif /* __SOC_TEGRA_COMMON_H__ */

0 comments on commit d506fc2

Please sign in to comment.