summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThierry Reding <treding@nvidia.com>2014-12-19 12:17:32 +0100
committerThierry Reding <treding@nvidia.com>2014-12-19 12:17:32 +0100
commitea133975e62d597bb53e14095eec59725e3d1345 (patch)
tree61f00891390c3be2b457342e3a291d97b72f8107
parentfa3dca4aefcfec97be61688457d3439ab7a7dd16 (diff)
parent268d23c16a4565ba183421e163cc3e98658280eb (diff)
Merge branch 'staging/powergate' into staging/master
-rw-r--r--arch/arm/boot/dts/tegra124.dtsi11
-rw-r--r--arch/arm/mach-tegra/Kconfig1
-rw-r--r--drivers/soc/tegra/pmc.c234
-rw-r--r--include/dt-bindings/power/tegra-powergate.h30
4 files changed, 275 insertions, 1 deletions
diff --git a/arch/arm/boot/dts/tegra124.dtsi b/arch/arm/boot/dts/tegra124.dtsi
index 3e2195163d9..0aa83575a0d 100644
--- a/arch/arm/boot/dts/tegra124.dtsi
+++ b/arch/arm/boot/dts/tegra124.dtsi
@@ -4,6 +4,7 @@
#include <dt-bindings/pinctrl/pinctrl-tegra.h>
#include <dt-bindings/pinctrl/pinctrl-tegra-xusb.h>
#include <dt-bindings/interrupt-controller/arm-gic.h>
+#include <dt-bindings/power/tegra-powergate.h>
#include <dt-bindings/thermal/tegra124-soctherm.h>
#include "skeleton.dtsi"
@@ -48,6 +49,7 @@
<&tegra_car 72>,
<&tegra_car 74>;
reset-names = "pex", "afi", "pcie_x";
+ power-domains = <&pmc TEGRA_POWERGATE_PCIE>;
status = "disabled";
phys = <&padctl TEGRA_XUSB_PADCTL_PCIE>;
@@ -98,6 +100,7 @@
compatible = "nvidia,tegra124-dc";
reg = <0x0 0x54200000 0x0 0x00040000>;
interrupts = <GIC_SPI 73 IRQ_TYPE_LEVEL_HIGH>;
+ power-domains = <&pmc TEGRA_POWERGATE_DIS>;
clocks = <&tegra_car TEGRA124_CLK_DISP1>,
<&tegra_car TEGRA124_CLK_PLL_P>;
clock-names = "dc", "parent";
@@ -113,6 +116,7 @@
compatible = "nvidia,tegra124-dc";
reg = <0x0 0x54240000 0x0 0x00040000>;
interrupts = <GIC_SPI 74 IRQ_TYPE_LEVEL_HIGH>;
+ power-domains = <&pmc TEGRA_POWERGATE_DISB>;
clocks = <&tegra_car TEGRA124_CLK_DISP2>,
<&tegra_car TEGRA124_CLK_PLL_P>;
clock-names = "dc", "parent";
@@ -147,6 +151,7 @@
clock-names = "sor", "parent", "dp", "safe";
resets = <&tegra_car 182>;
reset-names = "sor";
+ power-domains = <&pmc TEGRA_POWERGATE_SOR>;
status = "disabled";
};
@@ -542,11 +547,13 @@
clocks = <&tegra_car TEGRA124_CLK_RTC>;
};
- pmc@0,7000e400 {
+ pmc: pmc@0,7000e400 {
compatible = "nvidia,tegra124-pmc";
reg = <0x0 0x7000e400 0x0 0x400>;
clocks = <&tegra_car TEGRA124_CLK_PCLK>, <&clk32k_in>;
clock-names = "pclk", "clk32k_in";
+
+ #power-domain-cells = <1>;
};
fuse@0,7000f800 {
@@ -588,6 +595,8 @@
<&tegra_car 129>;
reset-names = "sata", "sata-oob", "sata-cold";
+ power-domains = <&pmc TEGRA_POWERGATE_SATA>;
+
phys = <&padctl TEGRA_XUSB_PADCTL_SATA>;
phy-names = "sata-phy";
diff --git a/arch/arm/mach-tegra/Kconfig b/arch/arm/mach-tegra/Kconfig
index 9a515824598..f87684e600c 100644
--- a/arch/arm/mach-tegra/Kconfig
+++ b/arch/arm/mach-tegra/Kconfig
@@ -9,6 +9,7 @@ menuconfig ARCH_TEGRA
select HAVE_ARM_SCU if SMP
select HAVE_ARM_TWD if SMP
select PINCTRL
+ select PM_GENERIC_DOMAINS if PM
select ARCH_HAS_RESET_CONTROLLER
select RESET_CONTROLLER
select SOC_BUS
diff --git a/drivers/soc/tegra/pmc.c b/drivers/soc/tegra/pmc.c
index a2c0ceb95f8..d36f8dc26d4 100644
--- a/drivers/soc/tegra/pmc.c
+++ b/drivers/soc/tegra/pmc.c
@@ -17,6 +17,8 @@
*
*/
+#define DEBUG
+
#include <linux/kernel.h>
#include <linux/clk.h>
#include <linux/clk/tegra.h>
@@ -29,8 +31,10 @@
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/platform_device.h>
+#include <linux/pm_domain.h>
#include <linux/reboot.h>
#include <linux/reset.h>
+#include <linux/sched.h>
#include <linux/seq_file.h>
#include <linux/spinlock.h>
@@ -83,6 +87,18 @@
#define GPU_RG_CNTRL 0x2d4
+struct tegra_powergate {
+ struct generic_pm_domain base;
+ struct tegra_pmc *pmc;
+ unsigned int id;
+};
+
+static inline struct tegra_powergate *
+to_powergate(struct generic_pm_domain *domain)
+{
+ return container_of(domain, struct tegra_powergate, base);
+}
+
struct tegra_pmc_soc {
unsigned int num_powergates;
const char *const *powergates;
@@ -92,8 +108,10 @@ struct tegra_pmc_soc {
/**
* struct tegra_pmc - NVIDIA Tegra PMC
+ * @dev: pointer to parent device
* @base: pointer to I/O remapped register region
* @clk: pointer to pclk clock
+ * @soc: SoC-specific data
* @rate: currently configured rate of pclk
* @suspend_mode: lowest suspend mode available
* @cpu_good_time: CPU power good time (in microseconds)
@@ -107,9 +125,12 @@ struct tegra_pmc_soc {
* @cpu_pwr_good_en: CPU power good signal is enabled
* @lp0_vec_phys: physical base address of the LP0 warm boot code
* @lp0_vec_size: size of the LP0 warm boot code
+ * @powergates: list of power gates
* @powergates_lock: mutex for power gate register access
+ * @nb: bus notifier for generic power domains
*/
struct tegra_pmc {
+ struct device *dev;
void __iomem *base;
struct clk *clk;
@@ -130,7 +151,9 @@ struct tegra_pmc {
u32 lp0_vec_phys;
u32 lp0_vec_size;
+ struct tegra_powergate *powergates;
struct mutex powergates_lock;
+ struct notifier_block nb;
};
static struct tegra_pmc *pmc = &(struct tegra_pmc) {
@@ -353,6 +376,8 @@ int tegra_pmc_cpu_remove_clamping(int cpuid)
if (id < 0)
return id;
+ usleep_range(10, 20);
+
return tegra_powergate_remove_clamping(id);
}
#endif /* CONFIG_SMP */
@@ -387,6 +412,130 @@ void tegra_pmc_restart(enum reboot_mode mode, const char *cmd)
tegra_pmc_writel(value, 0);
}
+static bool tegra_pmc_powergate_is_powered(struct tegra_powergate *powergate)
+{
+ u32 status = tegra_pmc_readl(PWRGATE_STATUS);
+
+ return (status & BIT(powergate->id)) != 0;
+}
+
+static int tegra_pmc_powergate_set(struct tegra_powergate *powergate,
+ bool new_state)
+{
+ u32 status, mask = new_state ? BIT(powergate->id) : 0;
+ bool state = false;
+
+ mutex_lock(&pmc->powergates_lock);
+
+ /* check the current state of the partition */
+ status = tegra_pmc_readl(PWRGATE_STATUS);
+ if (status & BIT(powergate->id))
+ state = true;
+
+ /* nothing to do */
+ if (new_state == state) {
+ mutex_unlock(&pmc->powergates_lock);
+ return 0;
+ }
+
+ /* toggle partition state and wait for state change to finish */
+ tegra_pmc_writel(PWRGATE_TOGGLE_START | powergate->id, PWRGATE_TOGGLE);
+
+ while (1) {
+ status = tegra_pmc_readl(PWRGATE_STATUS);
+ if ((status & BIT(powergate->id)) == mask)
+ break;
+
+ usleep_range(10, 20);
+ }
+
+ mutex_unlock(&pmc->powergates_lock);
+
+ return 0;
+}
+
+static int
+tegra_pmc_powergate_remove_clamping(struct tegra_powergate *powergate)
+{
+ u32 mask;
+
+ /*
+ * The Tegra124 GPU has a separate register (with different semantics)
+ * to remove clamps.
+ */
+ if (tegra_get_chip_id() == TEGRA124) {
+ if (powergate->id == TEGRA_POWERGATE_3D) {
+ tegra_pmc_writel(0, GPU_RG_CNTRL);
+ return 0;
+ }
+ }
+
+ /*
+ * Tegra 2 has a bug where PCIE and VDE clamping masks are
+ * swapped relatively to the partition ids
+ */
+ if (powergate->id == TEGRA_POWERGATE_VDEC)
+ mask = (1 << TEGRA_POWERGATE_PCIE);
+ else if (powergate->id == TEGRA_POWERGATE_PCIE)
+ mask = (1 << TEGRA_POWERGATE_VDEC);
+ else
+ mask = (1 << powergate->id);
+
+ tegra_pmc_writel(mask, REMOVE_CLAMPING);
+
+ return 0;
+}
+
+static int tegra_pmc_powergate_power_on(struct generic_pm_domain *domain)
+{
+ struct tegra_powergate *powergate = to_powergate(domain);
+ struct tegra_pmc *pmc = powergate->pmc;
+ int err = 0;
+
+ dev_dbg(pmc->dev, "> %s(domain=%p)\n", __func__, domain);
+ dev_dbg(pmc->dev, " name: %s\n", domain->name);
+
+ err = tegra_pmc_powergate_set(powergate, true);
+ if (err < 0)
+ goto out;
+
+ err = tegra_pmc_powergate_remove_clamping(powergate);
+
+out:
+ dev_dbg(pmc->dev, "< %s() = %d\n", __func__, err);
+ return err;
+}
+
+static int tegra_pmc_powergate_power_off(struct generic_pm_domain *domain)
+{
+ struct tegra_powergate *powergate = to_powergate(domain);
+ struct tegra_pmc *pmc = powergate->pmc;
+ int err = 0;
+
+ dev_dbg(pmc->dev, "> %s(domain=%p)\n", __func__, domain);
+ dev_dbg(pmc->dev, " name: %s\n", domain->name);
+
+ /* never turn off this partition */
+ switch (powergate->id) {
+ case TEGRA_POWERGATE_CPU:
+ case TEGRA_POWERGATE_CPU1:
+ case TEGRA_POWERGATE_CPU2:
+ case TEGRA_POWERGATE_CPU3:
+ case TEGRA_POWERGATE_CPU0:
+ case TEGRA_POWERGATE_C0NC:
+ case TEGRA_POWERGATE_IRAM:
+ dev_dbg(pmc->dev, "not disabling always-on partition %s\n",
+ domain->name);
+ goto out;
+ }
+
+ err = tegra_pmc_powergate_set(powergate, false);
+
+out:
+ dev_dbg(pmc->dev, "< %s() = %d\n", __func__, err);
+ return err;
+}
+
static int powergate_show(struct seq_file *s, void *data)
{
unsigned int i;
@@ -429,6 +578,81 @@ static int tegra_powergate_debugfs_init(void)
return 0;
}
+static struct generic_pm_domain *
+tegra_powergate_of_xlate(struct of_phandle_args *args, void *data)
+{
+ struct tegra_pmc *pmc = data;
+ unsigned int i;
+
+ dev_dbg(pmc->dev, "> %s(args=%p, data=%p)\n", __func__, args, data);
+
+ for (i = 0; i < pmc->soc->num_powergates; i++) {
+ struct tegra_powergate *powergate = &pmc->powergates[i];
+
+ if (!powergate->base.name)
+ continue;
+
+ if (powergate->id == args->args[0]) {
+ dev_dbg(pmc->dev, "< %s() = %p\n", __func__, powergate);
+ return &powergate->base;
+ }
+ }
+
+ dev_dbg(pmc->dev, "< %s() = -ENOENT\n", __func__);
+ return ERR_PTR(-ENOENT);
+}
+
+static int tegra_powergate_init(struct tegra_pmc *pmc)
+{
+ struct device_node *np = pmc->dev->of_node;
+ struct tegra_powergate *powergate;
+ unsigned int i;
+ int err = 0;
+
+ dev_dbg(pmc->dev, "> %s(pmc=%p)\n", __func__, pmc);
+
+ pmc->powergates = devm_kcalloc(pmc->dev, pmc->soc->num_powergates,
+ sizeof(*powergate), GFP_KERNEL);
+ if (!pmc->powergates) {
+ err = -ENOMEM;
+ goto out;
+ }
+
+ for (i = 0; i < pmc->soc->num_powergates; i++) {
+ bool off;
+
+ /* the powergate table may have gaps */
+ if (!pmc->soc->powergates[i])
+ continue;
+
+ powergate = &pmc->powergates[i];
+
+ powergate->base.name = kstrdup(pmc->soc->powergates[i],
+ GFP_KERNEL);
+ powergate->base.power_off = tegra_pmc_powergate_power_off;
+ powergate->base.power_on = tegra_pmc_powergate_power_on;
+
+ powergate->pmc = pmc;
+ powergate->id = i;
+
+ off = !tegra_pmc_powergate_is_powered(powergate);
+ pm_genpd_init(&powergate->base, NULL, off);
+ }
+
+ err = __of_genpd_add_provider(np, tegra_powergate_of_xlate, pmc);
+ if (err < 0)
+ return err;
+
+#if 0
+ pmc->nb.notifier_call = tegra_powergate_notifier_call;
+ bus_register_notifier(&platform_bus_type, &pmc->nb);
+#endif
+
+out:
+ dev_dbg(pmc->dev, "< %s() = %d\n", __func__, err);
+ return err;
+}
+
static int tegra_io_rail_prepare(int id, unsigned long *request,
unsigned long *status, unsigned int *bit)
{
@@ -709,6 +933,8 @@ static int tegra_pmc_probe(struct platform_device *pdev)
struct resource *res;
int err;
+ dev_dbg(&pdev->dev, "> %s(pdev=%p)\n", __func__, pdev);
+
err = tegra_pmc_parse_dt(pmc, pdev->dev.of_node);
if (err < 0)
return err;
@@ -728,14 +954,22 @@ static int tegra_pmc_probe(struct platform_device *pdev)
return err;
}
+ pmc->dev = &pdev->dev;
tegra_pmc_init(pmc);
+ if (0 && IS_ENABLED(CONFIG_PM_GENERIC_DOMAINS)) {
+ err = tegra_powergate_init(pmc);
+ if (err < 0)
+ return err;
+ }
+
if (IS_ENABLED(CONFIG_DEBUG_FS)) {
err = tegra_powergate_debugfs_init();
if (err < 0)
return err;
}
+ dev_dbg(&pdev->dev, "< %s()\n", __func__);
return 0;
}
diff --git a/include/dt-bindings/power/tegra-powergate.h b/include/dt-bindings/power/tegra-powergate.h
new file mode 100644
index 00000000000..b8265167c20
--- /dev/null
+++ b/include/dt-bindings/power/tegra-powergate.h
@@ -0,0 +1,30 @@
+#ifndef _DT_BINDINGS_POWER_TEGRA_POWERGATE_H
+#define _DT_BINDINGS_POWER_TEGRA_POWERGATE_H
+
+#define TEGRA_POWERGATE_CPU 0
+#define TEGRA_POWERGATE_3D 1
+#define TEGRA_POWERGATE_VENC 2
+#define TEGRA_POWERGATE_PCIE 3
+#define TEGRA_POWERGATE_VDEC 4
+#define TEGRA_POWERGATE_L2 5
+#define TEGRA_POWERGATE_MPE 6
+#define TEGRA_POWERGATE_HEG 7
+#define TEGRA_POWERGATE_SATA 8
+#define TEGRA_POWERGATE_CPU1 9
+#define TEGRA_POWERGATE_CPU2 10
+#define TEGRA_POWERGATE_CPU3 11
+#define TEGRA_POWERGATE_CELP 12
+#define TEGRA_POWERGATE_3D1 13
+#define TEGRA_POWERGATE_CPU0 14
+#define TEGRA_POWERGATE_C0NC 15
+#define TEGRA_POWERGATE_C1NC 16
+#define TEGRA_POWERGATE_SOR 17
+#define TEGRA_POWERGATE_DIS 18
+#define TEGRA_POWERGATE_DISB 19
+#define TEGRA_POWERGATE_XUSBA 20
+#define TEGRA_POWERGATE_XUSBB 21
+#define TEGRA_POWERGATE_XUSBC 22
+#define TEGRA_POWERGATE_VIC 23
+#define TEGRA_POWERGATE_IRAM 24
+
+#endif