summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--drivers/regulator/core.c23
-rw-r--r--drivers/soc/tegra/common.c97
-rw-r--r--drivers/soc/tegra/regulators-tegra20.c75
-rw-r--r--drivers/soc/tegra/regulators-tegra30.c75
-rw-r--r--include/linux/regulator/driver.h1
-rw-r--r--include/soc/tegra/common.h31
-rw-r--r--include/soc/tegra/fuse.h20
7 files changed, 317 insertions, 5 deletions
diff --git a/drivers/regulator/core.c b/drivers/regulator/core.c
index f192bf19492e..ead0b6d2af45 100644
--- a/drivers/regulator/core.c
+++ b/drivers/regulator/core.c
@@ -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
diff --git a/drivers/soc/tegra/common.c b/drivers/soc/tegra/common.c
index 3dc54f59cafe..cd33e99249c3 100644
--- a/drivers/soc/tegra/common.c
+++ b/drivers/soc/tegra/common.c
@@ -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", },
@@ -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);
diff --git a/drivers/soc/tegra/regulators-tegra20.c b/drivers/soc/tegra/regulators-tegra20.c
index 367a71a3cd10..3479be5ee494 100644
--- a/drivers/soc/tegra/regulators-tegra20.c
+++ b/drivers/soc/tegra/regulators-tegra20.c
@@ -12,6 +12,7 @@
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/of.h>
+#include <linux/reboot.h>
#include <linux/regulator/coupler.h>
#include <linux/regulator/driver.h>
#include <linux/regulator/machine.h>
@@ -21,7 +22,10 @@ struct tegra_regulator_coupler {
struct regulator_dev *core_rdev;
struct regulator_dev *cpu_rdev;
struct regulator_dev *rtc_rdev;
- int core_min_uV;
+ struct notifier_block reboot_notifier;
+ int core_min_uV, cpu_min_uV;
+ bool sys_reboot_mode_req;
+ bool sys_reboot_mode;
};
static inline struct tegra_regulator_coupler *
@@ -242,6 +246,10 @@ static int tegra20_cpu_voltage_update(struct tegra_regulator_coupler *tegra,
if (cpu_uV < 0)
return cpu_uV;
+ /* store boot voltage level */
+ if (!tegra->cpu_min_uV)
+ tegra->cpu_min_uV = cpu_uV;
+
/*
* CPU's regulator may not have any consumers, hence the voltage
* must not be changed in that case because CPU simply won't
@@ -250,6 +258,10 @@ static int tegra20_cpu_voltage_update(struct tegra_regulator_coupler *tegra,
if (!cpu_min_uV_consumers)
cpu_min_uV = cpu_uV;
+ /* restore boot voltage level */
+ if (tegra->sys_reboot_mode)
+ cpu_min_uV = max(cpu_min_uV, tegra->cpu_min_uV);
+
if (cpu_min_uV > cpu_uV) {
err = tegra20_core_rtc_update(tegra, core_rdev, rtc_rdev,
cpu_uV, cpu_min_uV);
@@ -290,6 +302,8 @@ static int tegra20_regulator_balance_voltage(struct regulator_coupler *coupler,
return -EINVAL;
}
+ tegra->sys_reboot_mode = READ_ONCE(tegra->sys_reboot_mode_req);
+
if (rdev == cpu_rdev)
return tegra20_cpu_voltage_update(tegra, cpu_rdev,
core_rdev, rtc_rdev);
@@ -303,6 +317,51 @@ static int tegra20_regulator_balance_voltage(struct regulator_coupler *coupler,
return -EPERM;
}
+static int tegra20_regulator_prepare_reboot(struct tegra_regulator_coupler *tegra,
+ bool sys_reboot_mode)
+{
+ int err;
+
+ if (!tegra->core_rdev || !tegra->rtc_rdev || !tegra->cpu_rdev)
+ return 0;
+
+ WRITE_ONCE(tegra->sys_reboot_mode_req, true);
+
+ /*
+ * Some devices use CPU soft-reboot method and in this case we
+ * should ensure that voltages are sane for the reboot by restoring
+ * the minimum boot levels.
+ */
+ err = regulator_sync_voltage_rdev(tegra->cpu_rdev);
+ if (err)
+ return err;
+
+ err = regulator_sync_voltage_rdev(tegra->core_rdev);
+ if (err)
+ return err;
+
+ WRITE_ONCE(tegra->sys_reboot_mode_req, sys_reboot_mode);
+
+ return 0;
+}
+
+static int tegra20_regulator_reboot(struct notifier_block *notifier,
+ unsigned long event, void *cmd)
+{
+ struct tegra_regulator_coupler *tegra;
+ int ret;
+
+ if (event != SYS_RESTART)
+ return NOTIFY_DONE;
+
+ tegra = container_of(notifier, struct tegra_regulator_coupler,
+ reboot_notifier);
+
+ ret = tegra20_regulator_prepare_reboot(tegra, true);
+
+ return notifier_from_errno(ret);
+}
+
static int tegra20_regulator_attach(struct regulator_coupler *coupler,
struct regulator_dev *rdev)
{
@@ -335,6 +394,14 @@ static int tegra20_regulator_detach(struct regulator_coupler *coupler,
{
struct tegra_regulator_coupler *tegra = to_tegra_coupler(coupler);
+ /*
+ * We don't expect regulators to be decoupled during reboot,
+ * this may race with the reboot handler and shouldn't ever
+ * happen in practice.
+ */
+ if (WARN_ON_ONCE(system_state > SYSTEM_RUNNING))
+ return -EPERM;
+
if (tegra->core_rdev == rdev) {
tegra->core_rdev = NULL;
return 0;
@@ -359,13 +426,19 @@ static struct tegra_regulator_coupler tegra20_coupler = {
.detach_regulator = tegra20_regulator_detach,
.balance_voltage = tegra20_regulator_balance_voltage,
},
+ .reboot_notifier.notifier_call = tegra20_regulator_reboot,
};
static int __init tegra_regulator_coupler_init(void)
{
+ int err;
+
if (!of_machine_is_compatible("nvidia,tegra20"))
return 0;
+ err = register_reboot_notifier(&tegra20_coupler.reboot_notifier);
+ WARN_ON(err);
+
return regulator_coupler_register(&tegra20_coupler.coupler);
}
arch_initcall(tegra_regulator_coupler_init);
diff --git a/drivers/soc/tegra/regulators-tegra30.c b/drivers/soc/tegra/regulators-tegra30.c
index 0e776b20f625..18fe53d0a870 100644
--- a/drivers/soc/tegra/regulators-tegra30.c
+++ b/drivers/soc/tegra/regulators-tegra30.c
@@ -12,6 +12,7 @@
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/of.h>
+#include <linux/reboot.h>
#include <linux/regulator/coupler.h>
#include <linux/regulator/driver.h>
#include <linux/regulator/machine.h>
@@ -22,7 +23,10 @@ struct tegra_regulator_coupler {
struct regulator_coupler coupler;
struct regulator_dev *core_rdev;
struct regulator_dev *cpu_rdev;
- int core_min_uV;
+ struct notifier_block reboot_notifier;
+ int core_min_uV, cpu_min_uV;
+ bool sys_reboot_mode_req;
+ bool sys_reboot_mode;
};
static inline struct tegra_regulator_coupler *
@@ -172,6 +176,10 @@ static int tegra30_voltage_update(struct tegra_regulator_coupler *tegra,
if (cpu_uV < 0)
return cpu_uV;
+ /* store boot voltage level */
+ if (!tegra->cpu_min_uV)
+ tegra->cpu_min_uV = cpu_uV;
+
/*
* CPU's regulator may not have any consumers, hence the voltage
* must not be changed in that case because CPU simply won't
@@ -195,6 +203,10 @@ static int tegra30_voltage_update(struct tegra_regulator_coupler *tegra,
if (err)
return err;
+ /* restore boot voltage level */
+ if (tegra->sys_reboot_mode)
+ cpu_min_uV = max(cpu_min_uV, tegra->cpu_min_uV);
+
if (core_min_limited_uV > core_uV) {
pr_err("core voltage constraint violated: %d %d %d\n",
core_uV, core_min_limited_uV, cpu_uV);
@@ -263,9 +275,56 @@ static int tegra30_regulator_balance_voltage(struct regulator_coupler *coupler,
return -EINVAL;
}
+ tegra->sys_reboot_mode = READ_ONCE(tegra->sys_reboot_mode_req);
+
return tegra30_voltage_update(tegra, cpu_rdev, core_rdev);
}
+static int tegra30_regulator_prepare_reboot(struct tegra_regulator_coupler *tegra,
+ bool sys_reboot_mode)
+{
+ int err;
+
+ if (!tegra->core_rdev || !tegra->cpu_rdev)
+ return 0;
+
+ WRITE_ONCE(tegra->sys_reboot_mode_req, true);
+
+ /*
+ * Some devices use CPU soft-reboot method and in this case we
+ * should ensure that voltages are sane for the reboot by restoring
+ * the minimum boot levels.
+ */
+ err = regulator_sync_voltage_rdev(tegra->cpu_rdev);
+ if (err)
+ return err;
+
+ err = regulator_sync_voltage_rdev(tegra->core_rdev);
+ if (err)
+ return err;
+
+ WRITE_ONCE(tegra->sys_reboot_mode_req, sys_reboot_mode);
+
+ return 0;
+}
+
+static int tegra30_regulator_reboot(struct notifier_block *notifier,
+ unsigned long event, void *cmd)
+{
+ struct tegra_regulator_coupler *tegra;
+ int ret;
+
+ if (event != SYS_RESTART)
+ return NOTIFY_DONE;
+
+ tegra = container_of(notifier, struct tegra_regulator_coupler,
+ reboot_notifier);
+
+ ret = tegra30_regulator_prepare_reboot(tegra, true);
+
+ return notifier_from_errno(ret);
+}
+
static int tegra30_regulator_attach(struct regulator_coupler *coupler,
struct regulator_dev *rdev)
{
@@ -292,6 +351,14 @@ static int tegra30_regulator_detach(struct regulator_coupler *coupler,
{
struct tegra_regulator_coupler *tegra = to_tegra_coupler(coupler);
+ /*
+ * We don't expect regulators to be decoupled during reboot,
+ * this may race with the reboot handler and shouldn't ever
+ * happen in practice.
+ */
+ if (WARN_ON_ONCE(system_state > SYSTEM_RUNNING))
+ return -EPERM;
+
if (tegra->core_rdev == rdev) {
tegra->core_rdev = NULL;
return 0;
@@ -311,13 +378,19 @@ static struct tegra_regulator_coupler tegra30_coupler = {
.detach_regulator = tegra30_regulator_detach,
.balance_voltage = tegra30_regulator_balance_voltage,
},
+ .reboot_notifier.notifier_call = tegra30_regulator_reboot,
};
static int __init tegra_regulator_coupler_init(void)
{
+ int err;
+
if (!of_machine_is_compatible("nvidia,tegra30"))
return 0;
+ err = register_reboot_notifier(&tegra30_coupler.reboot_notifier);
+ WARN_ON(err);
+
return regulator_coupler_register(&tegra30_coupler.coupler);
}
arch_initcall(tegra_regulator_coupler_init);
diff --git a/include/linux/regulator/driver.h b/include/linux/regulator/driver.h
index 4ea520c248e9..35e5a611db81 100644
--- a/include/linux/regulator/driver.h
+++ b/include/linux/regulator/driver.h
@@ -540,6 +540,7 @@ int regulator_set_current_limit_regmap(struct regulator_dev *rdev,
int regulator_get_current_limit_regmap(struct regulator_dev *rdev);
void *regulator_get_init_drvdata(struct regulator_init_data *reg_init_data);
int regulator_set_ramp_delay_regmap(struct regulator_dev *rdev, int ramp_delay);
+int regulator_sync_voltage_rdev(struct regulator_dev *rdev);
/*
* Helper functions intended to be used by regulator drivers prior registering
diff --git a/include/soc/tegra/common.h b/include/soc/tegra/common.h
index 98027a76ce3d..af41ad80ec21 100644
--- a/include/soc/tegra/common.h
+++ b/include/soc/tegra/common.h
@@ -6,6 +6,37 @@
#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 *params);
+#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 *params)
+{
+ return -ENODEV;
+}
+#endif
+
#endif /* __SOC_TEGRA_COMMON_H__ */
diff --git a/include/soc/tegra/fuse.h b/include/soc/tegra/fuse.h
index 78cbc787a4dc..990701f788bc 100644
--- a/include/soc/tegra/fuse.h
+++ b/include/soc/tegra/fuse.h
@@ -52,14 +52,28 @@ struct tegra_sku_info {
enum tegra_revision revision;
};
+#ifdef CONFIG_ARCH_TEGRA
+extern struct tegra_sku_info tegra_sku_info;
u32 tegra_read_straps(void);
u32 tegra_read_ram_code(void);
int tegra_fuse_readl(unsigned long offset, u32 *value);
-
-#ifdef CONFIG_ARCH_TEGRA
-extern struct tegra_sku_info tegra_sku_info;
#else
static struct tegra_sku_info tegra_sku_info __maybe_unused;
+
+static inline u32 tegra_read_straps(void)
+{
+ return 0;
+}
+
+static inline u32 tegra_read_ram_code(void)
+{
+ return 0;
+}
+
+static inline int tegra_fuse_readl(unsigned long offset, u32 *value)
+{
+ return -ENODEV;
+}
#endif
struct device *tegra_soc_device_register(void);