Skip to content

Commit

Permalink
Merge tag 'tegra-for-5.14-soc' of git://git.kernel.org/pub/scm/linux/…
Browse files Browse the repository at this point in the history
…kernel/git/tegra/linux into arm/soc

soc/tegra: Changes for v5.14-rc1

These changes implement the core power domain for the PMC, and fix a
couple of minor issues as well as add stubs to help some drivers be
compile tested more easily.

* tag 'tegra-for-5.14-soc' of git://git.kernel.org/pub/scm/linux/kernel/git/tegra/linux:
  soc/tegra: fuse: Fix Tegra234-only builds
  soc/tegra: fuse: Don't return -ENOMEM when allocate lookups failed
  soc/tegra: regulators: Support core domain state syncing
  soc/tegra: pmc: Add driver state syncing
  soc/tegra: pmc: Add core power domain
  soc/tegra: fuse: Add stubs needed for compile-testing
  soc/tegra: Add devm_tegra_core_dev_init_opp_table()
  soc/tegra: Add stub for soc_is_tegra()
  soc/tegra: regulators: Bump voltages on system reboot
  regulator: core: Add regulator_sync_voltage_rdev()

Link: https://lore.kernel.org/r/[email protected]
Signed-off-by: Olof Johansson <[email protected]>
  • Loading branch information
olofj committed Jun 15, 2021
2 parents 805be5c + e2d0ee2 commit 777cf27
Show file tree
Hide file tree
Showing 12 changed files with 509 additions and 12 deletions.
23 changes: 23 additions & 0 deletions drivers/regulator/core.c
Original file line number Diff line number Diff line change
Expand Up @@ -4105,6 +4105,29 @@ int regulator_set_voltage_time_sel(struct regulator_dev *rdev,
}
EXPORT_SYMBOL_GPL(regulator_set_voltage_time_sel);

int regulator_sync_voltage_rdev(struct regulator_dev *rdev)
{
int ret;

regulator_lock(rdev);

if (!rdev->desc->ops->set_voltage &&
!rdev->desc->ops->set_voltage_sel) {
ret = -EINVAL;
goto out;
}

/* balance only, if regulator is coupled */
if (rdev->coupling_desc.n_coupled > 1)
ret = regulator_balance_voltage(rdev, PM_SUSPEND_ON);
else
ret = -EOPNOTSUPP;

out:
regulator_unlock(rdev);
return ret;
}

/**
* regulator_sync_voltage - re-apply last regulator output voltage
* @regulator: regulator source
Expand Down
2 changes: 2 additions & 0 deletions drivers/soc/tegra/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,8 @@ config SOC_TEGRA_FLOWCTRL
config SOC_TEGRA_PMC
bool
select GENERIC_PINCONF
select PM_OPP
select PM_GENERIC_DOMAINS

config SOC_TEGRA_POWERGATE_BPMP
def_bool y
Expand Down
97 changes: 97 additions & 0 deletions drivers/soc/tegra/common.c
Original file line number Diff line number Diff line change
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,93 @@ bool soc_is_tegra(void)

return match != NULL;
}

static int tegra_core_dev_init_opp_state(struct device *dev)
{
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);
}

rate = clk_get_rate(clk);
if (!rate) {
dev_err(dev, "failed to get clk rate\n");
return -EINVAL;
}

/* first dummy rate-setting initializes voltage vote */
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
* @dev: device for which OPP table is initialized
* @params: 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)
{
u32 hw_version;
int err;

err = devm_pm_opp_set_clkname(dev, NULL);
if (err) {
dev_err(dev, "failed to set OPP clk: %d\n", err);
return err;
}

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

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

/*
* Older device-trees have an empty OPP table, we will get
* -ENODEV from devm_pm_opp_of_add_table() in this case.
*/
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);
6 changes: 2 additions & 4 deletions drivers/soc/tegra/fuse/fuse-tegra.c
Original file line number Diff line number Diff line change
Expand Up @@ -489,10 +489,8 @@ static int __init tegra_init_fuse(void)
size_t size = sizeof(*fuse->lookups) * fuse->soc->num_lookups;

fuse->lookups = kmemdup(fuse->soc->lookups, size, GFP_KERNEL);
if (!fuse->lookups)
return -ENOMEM;

nvmem_add_cell_lookups(fuse->lookups, fuse->soc->num_lookups);
if (fuse->lookups)
nvmem_add_cell_lookups(fuse->lookups, fuse->soc->num_lookups);
}

return 0;
Expand Down
3 changes: 2 additions & 1 deletion drivers/soc/tegra/fuse/fuse-tegra30.c
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@
defined(CONFIG_ARCH_TEGRA_132_SOC) || \
defined(CONFIG_ARCH_TEGRA_210_SOC) || \
defined(CONFIG_ARCH_TEGRA_186_SOC) || \
defined(CONFIG_ARCH_TEGRA_194_SOC)
defined(CONFIG_ARCH_TEGRA_194_SOC) || \
defined(CONFIG_ARCH_TEGRA_234_SOC)
static u32 tegra30_fuse_read_early(struct tegra_fuse *fuse, unsigned int offset)
{
if (WARN_ON(!fuse->base))
Expand Down
144 changes: 144 additions & 0 deletions drivers/soc/tegra/pmc.c
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
#include <linux/pinctrl/pinctrl.h>
#include <linux/platform_device.h>
#include <linux/pm_domain.h>
#include <linux/pm_opp.h>
#include <linux/reboot.h>
#include <linux/regmap.h>
#include <linux/reset.h>
Expand Down Expand Up @@ -428,6 +429,9 @@ struct tegra_pmc {
struct irq_chip irq;

struct notifier_block clk_nb;

bool core_domain_state_synced;
bool core_domain_registered;
};

static struct tegra_pmc *pmc = &(struct tegra_pmc) {
Expand Down Expand Up @@ -1302,12 +1306,107 @@ static int tegra_powergate_add(struct tegra_pmc *pmc, struct device_node *np)
return err;
}

bool tegra_pmc_core_domain_state_synced(void)
{
return pmc->core_domain_state_synced;
}

static int
tegra_pmc_core_pd_set_performance_state(struct generic_pm_domain *genpd,
unsigned int level)
{
struct dev_pm_opp *opp;
int err;

opp = dev_pm_opp_find_level_ceil(&genpd->dev, &level);
if (IS_ERR(opp)) {
dev_err(&genpd->dev, "failed to find OPP for level %u: %pe\n",
level, opp);
return PTR_ERR(opp);
}

mutex_lock(&pmc->powergates_lock);
err = dev_pm_opp_set_opp(pmc->dev, opp);
mutex_unlock(&pmc->powergates_lock);

dev_pm_opp_put(opp);

if (err) {
dev_err(&genpd->dev, "failed to set voltage to %duV: %d\n",
level, err);
return err;
}

return 0;
}

static unsigned int
tegra_pmc_core_pd_opp_to_performance_state(struct generic_pm_domain *genpd,
struct dev_pm_opp *opp)
{
return dev_pm_opp_get_level(opp);
}

static int tegra_pmc_core_pd_add(struct tegra_pmc *pmc, struct device_node *np)
{
struct generic_pm_domain *genpd;
const char *rname = "core";
int err;

genpd = devm_kzalloc(pmc->dev, sizeof(*genpd), GFP_KERNEL);
if (!genpd)
return -ENOMEM;

genpd->name = np->name;
genpd->set_performance_state = tegra_pmc_core_pd_set_performance_state;
genpd->opp_to_performance_state = tegra_pmc_core_pd_opp_to_performance_state;

err = devm_pm_opp_set_regulators(pmc->dev, &rname, 1);
if (err)
return dev_err_probe(pmc->dev, err,
"failed to set core OPP regulator\n");

err = pm_genpd_init(genpd, NULL, false);
if (err) {
dev_err(pmc->dev, "failed to init core genpd: %d\n", err);
return err;
}

err = of_genpd_add_provider_simple(np, genpd);
if (err) {
dev_err(pmc->dev, "failed to add core genpd: %d\n", err);
goto remove_genpd;
}

pmc->core_domain_registered = true;

return 0;

remove_genpd:
pm_genpd_remove(genpd);

return err;
}

static int tegra_powergate_init(struct tegra_pmc *pmc,
struct device_node *parent)
{
struct of_phandle_args child_args, parent_args;
struct device_node *np, *child;
int err = 0;

/*
* Core power domain is the parent of powergate domains, hence it
* should be registered first.
*/
np = of_get_child_by_name(parent, "core-domain");
if (np) {
err = tegra_pmc_core_pd_add(pmc, np);
of_node_put(np);
if (err)
return err;
}

np = of_get_child_by_name(parent, "powergates");
if (!np)
return 0;
Expand All @@ -1318,6 +1417,21 @@ static int tegra_powergate_init(struct tegra_pmc *pmc,
of_node_put(child);
break;
}

if (of_parse_phandle_with_args(child, "power-domains",
"#power-domain-cells",
0, &parent_args))
continue;

child_args.np = child;
child_args.args_count = 0;

err = of_genpd_add_subdomain(&parent_args, &child_args);
of_node_put(parent_args.np);
if (err) {
of_node_put(child);
break;
}
}

of_node_put(np);
Expand Down Expand Up @@ -1361,6 +1475,12 @@ static void tegra_powergate_remove_all(struct device_node *parent)
}

of_node_put(np);

np = of_get_child_by_name(parent, "core-domain");
if (np) {
of_genpd_del_provider(np);
of_genpd_remove_last(np);
}
}

static const struct tegra_io_pad_soc *
Expand Down Expand Up @@ -3672,6 +3792,29 @@ static const struct of_device_id tegra_pmc_match[] = {
{ }
};

static void tegra_pmc_sync_state(struct device *dev)
{
int err;

/*
* Older device-trees don't have core PD, and thus, there are
* no dependencies that will block the state syncing. We shouldn't
* mark the domain as synced in this case.
*/
if (!pmc->core_domain_registered)
return;

pmc->core_domain_state_synced = true;

/* this is a no-op if core regulator isn't used */
mutex_lock(&pmc->powergates_lock);
err = dev_pm_opp_sync_regulators(dev);
mutex_unlock(&pmc->powergates_lock);

if (err)
dev_err(dev, "failed to sync regulators: %d\n", err);
}

static struct platform_driver tegra_pmc_driver = {
.driver = {
.name = "tegra-pmc",
Expand All @@ -3680,6 +3823,7 @@ static struct platform_driver tegra_pmc_driver = {
#if defined(CONFIG_PM_SLEEP) && defined(CONFIG_ARM)
.pm = &tegra_pmc_pm_ops,
#endif
.sync_state = tegra_pmc_sync_state,
},
.probe = tegra_pmc_probe,
};
Expand Down
Loading

0 comments on commit 777cf27

Please sign in to comment.