Skip to contentSkip to conversation

Add generic PM domain support for Tegra SoCs

PATCH Superseded Jon Hunter 4 participants 54 messages Jul 13, 2015 – Jul 28, 2015

Subsystems (12)

v3 of 5 Superseded Latest is v5 →

lore
Share & tools
lore

Jon Hunter

author maintainer
#0 lore raw

v3 cover Add generic PM domain support for Tegra SoCs

This series is based upon the V2 series from Vince Hsu [1]. The code for the MC flush operations and PMC code for the generic PM domains has been re-written to simplify and consolidate legacy/existing support for power-domains on the Tegra SoC.

This series has been boot tested on various 32-bit Tegra platforms as well as compile tested for 64-bit ARM. A summary of the results is available here [2]. Would be great to get some more testing for PCI, SATA, GR3D and DRM.

Summary:

Patch 1:     Adds the new function of_reset_control_get_by_index().
Patch 2-5:   Add the MC flush operations for Tegra30/Tegra114/Tegra124.
Patch 6:     Is a PLLD fix for Tegra114.
Patch 7-9:   PMC preparation for generic PM domains.
Patch 10-13: Update drivers to support generic PM domains
Patch 14:    Add DT documentation for tegra generic PM domains
Patch 15:    Add PMC generic DM domains support
Patch 16:    Remove legacy functions
Patch 17-18: Add DT bindings for tegra124
Patch 19:    Enable generic PM domains for tegra

Changes:
v3:
* Re-wrote and simplified MC flush code
* Re-wrote PMC code for generic PM domains to permit devices to use legacy DT blobs without generic PM domain info.
* Re-worked DRM, PCI, AHCI and GR3D drivers to allow then to work with and without generic PM domains.
* Added DT documentation for tegra generic PM domain bindings.
* Dropped DT bindings for T30 and T114 (plan to add later)

v2 (Vince Hsu):
* Add support for Tegra30/Tegra114
* Address Alex's comments

[1] https://lkml.org/lkml/2015/3/12/284
[2] https://nvtb.github.io//epmt/jonathanh-tester/test_20150713051933_58fc293c9467f52c51f1e52e5d5a8da174b8550a/20150713051933/

Jon Hunter (12):
memory: tegra: Add MC flush support
soc: tegra: pmc: Wait for powergate state to change
soc: tegra: pmc: Clean-up PMC helper functions
soc: tegra: pmc: Prepare for migrating to generic PM domains drm/tegra: dc: Prepare for generic PM domains
PCI: tegra: Add support for generic PM domains
ata: ahci_tegra: Add support for generic PM domains drm/tegra: gr3d: Add support for generic PM domains
Documentation: DT: bindings: Add power domain info for NVIDIA PMC
soc: tegra: pmc: Add generic PM domain support
soc: tegra: pmc: Remove the deprecated powergate APIs
ARM: tegra: Add PM domain device nodes to Tegra124 DT

Vince Hsu (7):
reset: add of_reset_control_get_by_index()
memory: tegra: add flush operation for Tegra30 memory clients
memory: tegra: add flush operation for Tegra114 memory clients
memory: tegra: add flush operation for Tegra124 memory clients
clk: tegra: remove TEGRA_PLL_USE_LOCK for PLLD/PLLD2
ARM: tegra: add GPU power supply to Jetson TK1 DT
ARM: tegra: select PM_GENERIC_DOMAINS

 .../bindings/arm/tegra/nvidia,tegra20-pmc.txt      |  99 ++++
 arch/arm/boot/dts/tegra124-jetson-tk1.dts          |   6 +-
 arch/arm/boot/dts/tegra124.dtsi                    |  80 +++
 arch/arm/mach-tegra/Kconfig                        |   1 +
 arch/arm/mach-tegra/platsmp.c                      |  18 +-
 drivers/ata/ahci_tegra.c                           |  51 +-
 drivers/clk/tegra/clk-tegra114.c                   |   6 +-
 drivers/gpu/drm/tegra/dc.c                         |  94 +--
 drivers/gpu/drm/tegra/gr3d.c                       |  57 +-
 drivers/memory/tegra/mc.c                          | 110 ++++
 drivers/memory/tegra/mc.h                          |   2 +
 drivers/memory/tegra/tegra114.c                    |  21 +
 drivers/memory/tegra/tegra124.c                    |  31 +
 drivers/memory/tegra/tegra30.c                     |  24 +
 drivers/pci/host/pci-tegra.c                       |  49 +-
 drivers/reset/core.c                               |  40 +-
 drivers/soc/tegra/pmc.c                            | 647 ++++++++++++++++++---
 include/dt-bindings/power/tegra-powergate.h        |  42 ++
 include/linux/reset.h                              |   9 +
 include/soc/tegra/mc.h                             |  34 ++
 include/soc/tegra/pmc.h                            |  74 +--
 21 files changed, 1264 insertions(+), 231 deletions(-)
 create mode 100644 include/dt-bindings/power/tegra-powergate.h
Signature · 1 line
2.1.4
author maintainer
#1 lore raw

PATCH v3 1/19 reset: add of_reset_control_get_by_index()

From: Vince Hsu <[email protected]>

Add of_reset_control_get_by_index() to allow the drivers to get reset device without knowing its name.

[jonathanh at nvidia.com: Updated stub function to return -ENOTSUPP instead
of -ENOSYS which should only be used for system calls.]

---
v3: addressed comments from Philipp
v2: minor changes according to Alex's comments

---
 drivers/reset/core.c  | 40 +++++++++++++++++++++++++++++-----------
 include/linux/reset.h |  9 +++++++++
 2 files changed, 38 insertions(+), 11 deletions(-)
drivers/reset/core.c +29 -11
index 7955e00d04d4..81ae17d15480 100644
@@ -141,27 +141,24 @@ int reset_control_status(struct reset_control *rstc)
 EXPORT_SYMBOL_GPL(reset_control_status);
 
 /**
- * of_reset_control_get - Lookup and obtain a reference to a reset controller.
+ * of_reset_control_get_by_index - Lookup and obtain a reference to a reset
+ * controller by index.
  * @node: device to be reset by the controller
- * @id: reset line name
- *
- * Returns a struct reset_control or IS_ERR() condition containing errno.
+ * @index: index of the reset controller
  *
- * Use of id names is optional.
+ * This is to be used to perform a list of resets for a device or power domain
+ * in whatever order. Returns a struct reset_control or IS_ERR() condition
+ * containing errno.
  */
-struct reset_control *of_reset_control_get(struct device_node *node,
-					   const char *id)
+struct reset_control *of_reset_control_get_by_index(struct device_node *node,
+					   int index)
 {
 	struct reset_control *rstc = ERR_PTR(-EPROBE_DEFER);
 	struct reset_controller_dev *r, *rcdev;
 	struct of_phandle_args args;
-	int index = 0;
 	int rstc_id;
 	int ret;
 
-	if (id)
-		index = of_property_match_string(node,
-						 "reset-names", id);
 	ret = of_parse_phandle_with_args(node, "resets", "#reset-cells",
 					 index, &args);
 	if (ret)
@@ -202,6 +199,27 @@ struct reset_control *of_reset_control_get(struct device_node *node,
 
 	return rstc;
 }
+EXPORT_SYMBOL_GPL(of_reset_control_get_by_index);
+
+/**
+ * of_reset_control_get - Lookup and obtain a reference to a reset controller.
+ * @node: device to be reset by the controller
+ * @id: reset line name
+ *
+ * Returns a struct reset_control or IS_ERR() condition containing errno.
+ *
+ * Use of id names is optional.
+ */
+struct reset_control *of_reset_control_get(struct device_node *node,
+					   const char *id)
+{
+	int index = 0;
+
+	if (id)
+		index = of_property_match_string(node,
+						 "reset-names", id);
+	return of_reset_control_get_by_index(node, index);
+}
 EXPORT_SYMBOL_GPL(of_reset_control_get);
 
 /**
include/linux/reset.h +9 -0
index da5602bd77d7..26f1b53386d8 100644
@@ -38,6 +38,9 @@ static inline struct reset_control *devm_reset_control_get_optional(
 struct reset_control *of_reset_control_get(struct device_node *node,
 					   const char *id);
 
+struct reset_control *of_reset_control_get_by_index(
+					struct device_node *node, int index);
+
 #else
 
 static inline int reset_control_reset(struct reset_control *rstc)
@@ -92,6 +95,12 @@ static inline struct reset_control *of_reset_control_get(
 	return ERR_PTR(-ENOSYS);
 }
 
+static inline struct reset_control *of_reset_control_get_by_index(
+				struct device_node *node, int index)
+{
+	return ERR_PTR(-ENOTSUPP);
+}
+
 #endif /* CONFIG_RESET_CONTROLLER */
 
 #endif
Signature · 1 line
2.1.4
maintainer
#2 lore raw

PATCH v3 1/19 reset: add of_reset_control_get_by_index()

Am Montag, den 13.07.2015, 13:39 +0100 schrieb Jon Hunter:

Quoted 9 lines

From: Vince Hsu <[email protected]>

Add of_reset_control_get_by_index() to allow the drivers to get reset device without knowing its name.

[jonathanh at nvidia.com: Updated stub function to return -ENOTSUPP instead
of -ENOSYS which should only be used for system calls.]

regards
Philipp

Quoted 103 lines

---
v3: addressed comments from Philipp
v2: minor changes according to Alex's comments

---
 drivers/reset/core.c  | 40 +++++++++++++++++++++++++++++-----------
 include/linux/reset.h |  9 +++++++++
 2 files changed, 38 insertions(+), 11 deletions(-)
drivers/reset/core.c +29 -11
index 7955e00d04d4..81ae17d15480 100644
@@ -141,27 +141,24 @@ int reset_control_status(struct reset_control *rstc)
 EXPORT_SYMBOL_GPL(reset_control_status);
 
 /**
- * of_reset_control_get - Lookup and obtain a reference to a reset controller.
+ * of_reset_control_get_by_index - Lookup and obtain a reference to a reset
+ * controller by index.
  * @node: device to be reset by the controller
- * @id: reset line name
- *
- * Returns a struct reset_control or IS_ERR() condition containing errno.
+ * @index: index of the reset controller
  *
- * Use of id names is optional.
+ * This is to be used to perform a list of resets for a device or power domain
+ * in whatever order. Returns a struct reset_control or IS_ERR() condition
+ * containing errno.
  */
-struct reset_control *of_reset_control_get(struct device_node *node,
-					   const char *id)
+struct reset_control *of_reset_control_get_by_index(struct device_node *node,
+					   int index)
 {
 	struct reset_control *rstc = ERR_PTR(-EPROBE_DEFER);
 	struct reset_controller_dev *r, *rcdev;
 	struct of_phandle_args args;
-	int index = 0;
 	int rstc_id;
 	int ret;
 
-	if (id)
-		index = of_property_match_string(node,
-						 "reset-names", id);
 	ret = of_parse_phandle_with_args(node, "resets", "#reset-cells",
 					 index, &args);
 	if (ret)
@@ -202,6 +199,27 @@ struct reset_control *of_reset_control_get(struct device_node *node,
 
 	return rstc;
 }
+EXPORT_SYMBOL_GPL(of_reset_control_get_by_index);
+
+/**
+ * of_reset_control_get - Lookup and obtain a reference to a reset controller.
+ * @node: device to be reset by the controller
+ * @id: reset line name
+ *
+ * Returns a struct reset_control or IS_ERR() condition containing errno.
+ *
+ * Use of id names is optional.
+ */
+struct reset_control *of_reset_control_get(struct device_node *node,
+					   const char *id)
+{
+	int index = 0;
+
+	if (id)
+		index = of_property_match_string(node,
+						 "reset-names", id);
+	return of_reset_control_get_by_index(node, index);
+}
 EXPORT_SYMBOL_GPL(of_reset_control_get);
 
 /**
include/linux/reset.h +9 -0
index da5602bd77d7..26f1b53386d8 100644
@@ -38,6 +38,9 @@ static inline struct reset_control *devm_reset_control_get_optional(
 struct reset_control *of_reset_control_get(struct device_node *node,
 					   const char *id);
 
+struct reset_control *of_reset_control_get_by_index(
+					struct device_node *node, int index);
+
 #else
 
 static inline int reset_control_reset(struct reset_control *rstc)
@@ -92,6 +95,12 @@ static inline struct reset_control *of_reset_control_get(
 	return ERR_PTR(-ENOSYS);
 }
 
+static inline struct reset_control *of_reset_control_get_by_index(
+				struct device_node *node, int index)
+{
+	return ERR_PTR(-ENOTSUPP);
+}
+
 #endif /* CONFIG_RESET_CONTROLLER */
 
 #endif
author maintainer
#3 lore raw

PATCH v3 2/19 memory: tegra: Add MC flush support

In reply to Jon Hunter

The Tegra memory controller implements a flush feature to flush pending accesses and prevent further accesses from occurring. This feature is used when powering down IP blocks to ensure the IP block is in a good state. The flushes are organised by software groups and IP blocks are assigned in hardware to the different software groups. Add helper functions for requesting a handle to an MC flush for a given software group and enabling/disabling the MC flush itself.

This is based upon a change by Vince Hsu <[email protected]>.

---
 drivers/memory/tegra/mc.c | 110 ++++++++++++++++++++++++++++++++++++++++++++++
 drivers/memory/tegra/mc.h |   2 +
 include/soc/tegra/mc.h    |  34 ++++++++++++++
 3 files changed, 146 insertions(+)
drivers/memory/tegra/mc.c +110 -0
index c71ede67e6c8..fb8da3d4caf4 100644
@@ -7,6 +7,7 @@
  */
 
 #include <linux/clk.h>
+#include <linux/delay.h>
 #include <linux/interrupt.h>
 #include <linux/kernel.h>
 #include <linux/module.h>
@@ -71,6 +72,107 @@ static const struct of_device_id tegra_mc_of_match[] = {
 };
 MODULE_DEVICE_TABLE(of, tegra_mc_of_match);
 
+const struct tegra_mc_flush *tegra_mc_flush_get(struct tegra_mc *mc,
+						unsigned int swgroup)
+{
+	const struct tegra_mc_flush *flush = NULL;
+	int i;
+
+	mutex_lock(&mc->lock);
+
+	for (i = 0; i < mc->soc->num_flushes; i++) {
+		if (mc->soc->flushes[i].swgroup == swgroup) {
+			if (mc->flush_reserved[i] == false) {
+				mc->flush_reserved[i] = true;
+				flush = &mc->soc->flushes[i];
+			}
+			break;
+		}
+	}
+
+	mutex_unlock(&mc->lock);
+
+	return flush;
+}
+EXPORT_SYMBOL(tegra_mc_flush_get);
+
+static bool tegra_mc_flush_done(struct tegra_mc *mc,
+				const struct tegra_mc_flush *flush)
+{
+	unsigned long timeout = jiffies + msecs_to_jiffies(100);
+	int i;
+	u32 val;
+
+	while (time_before(jiffies, timeout)) {
+		val = mc_readl(mc, flush->status);
+
+		/*
+		 * If the flush bit is still set it
+		 * is not done and so wait then retry.
+		 */
+		if (val & BIT(flush->bit))
+			goto retry;
+
+		/*
+		 * Depending on the tegra SoC, it may be necessary to read
+		 * the status register multiple times to ensure the value
+		 * read is correct. Some tegra devices have a HW issue where
+		 * reading the status register shortly after writing the
+		 * control register (on the order of 5 cycles) may return
+		 * an incorrect value.
+		 */
+		for (i = 0; i < mc->soc->metastable_flush_reads; i++) {
+			if (mc_readl(mc, flush->status) != val)
+				goto retry;
+		}
+
+		/*
+		 * The flush is complete and so return.
+		 */
+		return 0;
+retry:
+		udelay(10);
+	}
+
+	return -ETIMEDOUT;
+}
+
+int tegra_mc_flush(struct tegra_mc *mc, const struct tegra_mc_flush *flush,
+		   bool enable)
+{
+	int ret = 0;
+	u32 val;
+
+	if (!mc || !flush)
+		return -EINVAL;
+
+	mutex_lock(&mc->lock);
+
+	val = mc_readl(mc, flush->ctrl);
+
+	if (enable)
+		val |= BIT(flush->bit);
+	else
+		val &= ~BIT(flush->bit);
+
+	mc_writel(mc, val, flush->ctrl);
+	mc_readl(mc, flush->ctrl);
+
+	/*
+	 * If activating the flush, poll the
+	 * status register until the flush is done.
+	 */
+	if (enable)
+		ret = tegra_mc_flush_done(mc, flush);
+
+	mutex_unlock(&mc->lock);
+
+	dev_dbg(mc->dev, "%s bit %d\n", __func__, flush->bit);
+
+	return ret;
+}
+EXPORT_SYMBOL(tegra_mc_flush);
+
 static int tegra_mc_setup_latency_allowance(struct tegra_mc *mc)
 {
 	unsigned long long tick;
@@ -359,6 +461,12 @@ static int tegra_mc_probe(struct platform_device *pdev)
 	mc->soc = match->data;
 	mc->dev = &pdev->dev;
 
+	mc->flush_reserved = devm_kcalloc(&pdev->dev, mc->soc->num_flushes,
+					  sizeof(mc->flush_reserved),
+					  GFP_KERNEL);
+	if (!mc->flush_reserved)
+		return -ENOMEM;
+
 	/* length of MC tick in nanoseconds */
 	mc->tick = 30;
 
@@ -410,6 +518,8 @@ static int tegra_mc_probe(struct platform_device *pdev)
 		return err;
 	}
 
+	mutex_init(&mc->lock);
+
 	value = MC_INT_DECERR_MTS | MC_INT_SECERR_SEC | MC_INT_DECERR_VPR |
 		MC_INT_INVALID_APB_ASID_UPDATE | MC_INT_INVALID_SMMU_PAGE |
 		MC_INT_SECURITY_VIOLATION | MC_INT_DECERR_EMEM;
drivers/memory/tegra/mc.h +2 -0
index b7361b0a6696..0f59d49b735b 100644
@@ -14,6 +14,8 @@
 
 #include <soc/tegra/mc.h>
 
+#define MC_FLUSH_METASTABLE_READS	5
+
 static inline u32 mc_readl(struct tegra_mc *mc, unsigned long offset)
 {
 	return readl(mc->regs + offset);
include/soc/tegra/mc.h +34 -0
index 1ab2813273cd..b634c6df79eb 100644
@@ -45,6 +45,13 @@ struct tegra_mc_client {
 	struct tegra_mc_la la;
 };
 
+struct tegra_mc_flush {
+	unsigned int swgroup;
+	unsigned int ctrl;
+	unsigned int status;
+	unsigned int bit;
+};
+
 struct tegra_smmu_swgroup {
 	const char *name;
 	unsigned int swgroup;
@@ -96,6 +103,10 @@ struct tegra_mc_soc {
 	const struct tegra_mc_client *clients;
 	unsigned int num_clients;
 
+	const struct tegra_mc_flush *flushes;
+	unsigned int num_flushes;
+	unsigned int metastable_flush_reads;
+
 	const unsigned long *emem_regs;
 	unsigned int num_emem_regs;
 
@@ -117,9 +128,32 @@ struct tegra_mc {
 
 	struct tegra_mc_timing *timings;
 	unsigned int num_timings;
+
+	bool *flush_reserved;
+
+	struct mutex lock;
 };
 
 void tegra_mc_write_emem_configuration(struct tegra_mc *mc, unsigned long rate);
 unsigned int tegra_mc_get_emem_device_count(struct tegra_mc *mc);
 
+#ifdef CONFIG_TEGRA_MC
+const struct tegra_mc_flush *tegra_mc_flush_get(struct tegra_mc *mc,
+						unsigned int swgroup);
+int tegra_mc_flush(struct tegra_mc *mc, const struct tegra_mc_flush *s,
+		   bool enable);
+#else
+const struct tegra_mc_flush *tegra_mc_flush_get(struct tegra_mc *mc,
+						unsigned int swgroup)
+{
+	return NULL;
+}
+
+int tegra_mc_flush(struct tegra_mc *mc, const struct tegra_mc_flush *s,
+		   bool enable)
+{
+	return -ENOTSUPP;
+}
+#endif
+
 #endif /* __SOC_TEGRA_MC_H__ */
Signature · 1 line
2.1.4
#4 lore raw

PATCH v3 2/19 memory: tegra: Add MC flush support

Quoted 8 lines · Jon Hunter

The Tegra memory controller implements a flush feature to flush pending accesses and prevent further accesses from occurring. This feature is used when powering down IP blocks to ensure the IP block is in a good state. The flushes are organised by software groups and IP blocks are assigned in hardware to the different software groups. Add helper functions for requesting a handle to an MC flush for a given software group and enabling/disabling the MC flush itself.

Quoted 8 lines

This is based upon a change by Vince Hsu <[email protected]>.

---
 drivers/memory/tegra/mc.c | 110 ++++++++++++++++++++++++++++++++++++++++++++++
 drivers/memory/tegra/mc.h |   2 +
 include/soc/tegra/mc.h    |  34 ++++++++++++++
 3 files changed, 146 insertions(+)

Do we know if this is actually necessary? I remember having a discussion with Arnd Bergmann a while ago, and the Linux driver model kind of assumes that by the time a device is disabled all outstanding accesses will have stopped.

Do we have a way to determine that this even makes a difference? Can we trigger a case where not doing this would cause breakage and see that adding this fixes that particular issue?

Thierry
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 819 bytes
Desc: not available
URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20150717/ffe99bd6/attachment.sig>

#5 lore raw

PATCH v3 2/19 memory: tegra: Add MC flush support

Quoted 20 lines · Thierry Reding

* PGP Signed by an unknown key

Quoted 8 lines · Jon Hunter

The Tegra memory controller implements a flush feature to flush pending accesses and prevent further accesses from occurring. This feature is used when powering down IP blocks to ensure the IP block is in a good state. The flushes are organised by software groups and IP blocks are assigned in hardware to the different software groups. Add helper functions for requesting a handle to an MC flush for a given software group and enabling/disabling the MC flush itself.

Quoted 8 lines

This is based upon a change by Vince Hsu <[email protected]>.

---
 drivers/memory/tegra/mc.c | 110 ++++++++++++++++++++++++++++++++++++++++++++++
 drivers/memory/tegra/mc.h |   2 +
 include/soc/tegra/mc.h    |  34 ++++++++++++++
 3 files changed, 146 insertions(+)
Quoted 9 lines

Do we know if this is actually necessary? I remember having a discussion with Arnd Bergmann a while ago, and the Linux driver model kind of assumes that by the time a device is disabled all outstanding accesses will have stopped.

Do we have a way to determine that this even makes a difference? Can we trigger a case where not doing this would cause breakage and see that adding this fixes that particular issue?

Most likely it is. The memory controller can still be processing requests when the peripheral domain is powergated. This would mean the response cannot be delivered in that case. So we need to be sure there are no outstanding requests before shutting down the domain.

Cheers,

Peter.

#6 lore raw

PATCH v3 2/19 memory: tegra: Add MC flush support

Quoted 31 lines · Peter De Schrijver
Quoted 20 lines · Thierry Reding

* PGP Signed by an unknown key

Quoted 8 lines · Jon Hunter

The Tegra memory controller implements a flush feature to flush pending accesses and prevent further accesses from occurring. This feature is used when powering down IP blocks to ensure the IP block is in a good state. The flushes are organised by software groups and IP blocks are assigned in hardware to the different software groups. Add helper functions for requesting a handle to an MC flush for a given software group and enabling/disabling the MC flush itself.

Quoted 8 lines

This is based upon a change by Vince Hsu <[email protected]>.

---
 drivers/memory/tegra/mc.c | 110 ++++++++++++++++++++++++++++++++++++++++++++++
 drivers/memory/tegra/mc.h |   2 +
 include/soc/tegra/mc.h    |  34 ++++++++++++++
 3 files changed, 146 insertions(+)
Quoted 9 lines

Do we know if this is actually necessary? I remember having a discussion with Arnd Bergmann a while ago, and the Linux driver model kind of assumes that by the time a device is disabled all outstanding accesses will have stopped.

Do we have a way to determine that this even makes a difference? Can we trigger a case where not doing this would cause breakage and see that adding this fixes that particular issue?

Quoted 4 lines

Most likely it is. The memory controller can still be processing requests when the peripheral domain is powergated. This would mean the response cannot be delivered in that case. So we need to be sure there are no outstanding requests before shutting down the domain.

My point is that that's the driver's responsibility anyway, hence making the explicit flush unnecessary.

Thierry
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 819 bytes
Desc: not available
URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20150717/78b5bd92/attachment-0001.sig>

author maintainer
#7 lore raw

PATCH v3 2/19 memory: tegra: Add MC flush support

Quoted 34 lines · Thierry Reding

* PGP Signed by an unknown key

Quoted 31 lines · Peter De Schrijver
Quoted 29 lines · Thierry Reding
Quoted 1 line

Old Signed by an unknown key

Quoted 8 lines · Jon Hunter

The Tegra memory controller implements a flush feature to flush pending accesses and prevent further accesses from occurring. This feature is used when powering down IP blocks to ensure the IP block is in a good state. The flushes are organised by software groups and IP blocks are assigned in hardware to the different software groups. Add helper functions for requesting a handle to an MC flush for a given software group and enabling/disabling the MC flush itself.

Quoted 8 lines

This is based upon a change by Vince Hsu <[email protected]>.

---
 drivers/memory/tegra/mc.c | 110 ++++++++++++++++++++++++++++++++++++++++++++++
 drivers/memory/tegra/mc.h |   2 +
 include/soc/tegra/mc.h    |  34 ++++++++++++++
 3 files changed, 146 insertions(+)

Do we know if this is actually necessary? I remember having a discussion with Arnd Bergmann a while ago, and the Linux driver model kind of assumes that by the time a device is disabled all outstanding accesses will have stopped.

Do we have a way to determine that this even makes a difference? Can we trigger a case where not doing this would cause breakage and see that adding this fixes that particular issue?

Quoted 7 lines
Quoted 4 lines

Most likely it is. The memory controller can still be processing requests when the peripheral domain is powergated. This would mean the response cannot be delivered in that case. So we need to be sure there are no outstanding requests before shutting down the domain.

My point is that that's the driver's responsibility anyway, hence making the explicit flush unnecessary.

I see your point and it is interesting. The trouble is that we would need to test every memory client in every power domain to prove this. So I don't think that is a trivial thing to do. Furthermore, looking at what we have done in kernel used for android products (which probably stress PM the most) this is done and so I don't know of any shipping product that stresses PM that does not do this. May be someone else might. I personally would not be comfortable removing this without testing, but as I mentioned it is not a trivial thing to test correctly. However, I will let you and the other maintainers decide what's best here.

Jon

#8 lore raw

PATCH v3 2/19 memory: tegra: Add MC flush support

Quoted 44 lines · Jon Hunter
Quoted 34 lines · Thierry Reding

* PGP Signed by an unknown key

Quoted 31 lines · Peter De Schrijver
Quoted 29 lines · Thierry Reding
Quoted 1 line

Old Signed by an unknown key

Quoted 8 lines · Jon Hunter

The Tegra memory controller implements a flush feature to flush pending accesses and prevent further accesses from occurring. This feature is used when powering down IP blocks to ensure the IP block is in a good state. The flushes are organised by software groups and IP blocks are assigned in hardware to the different software groups. Add helper functions for requesting a handle to an MC flush for a given software group and enabling/disabling the MC flush itself.

Quoted 8 lines

This is based upon a change by Vince Hsu <[email protected]>.

---
 drivers/memory/tegra/mc.c | 110 ++++++++++++++++++++++++++++++++++++++++++++++
 drivers/memory/tegra/mc.h |   2 +
 include/soc/tegra/mc.h    |  34 ++++++++++++++
 3 files changed, 146 insertions(+)

Do we know if this is actually necessary? I remember having a discussion with Arnd Bergmann a while ago, and the Linux driver model kind of assumes that by the time a device is disabled all outstanding accesses will have stopped.

Do we have a way to determine that this even makes a difference? Can we trigger a case where not doing this would cause breakage and see that adding this fixes that particular issue?

Quoted 7 lines
Quoted 4 lines

Most likely it is. The memory controller can still be processing requests when the peripheral domain is powergated. This would mean the response cannot be delivered in that case. So we need to be sure there are no outstanding requests before shutting down the domain.

My point is that that's the driver's responsibility anyway, hence making the explicit flush unnecessary.

Quoted 9 lines

I see your point and it is interesting. The trouble is that we would need to test every memory client in every power domain to prove this. So I don't think that is a trivial thing to do. Furthermore, looking at what we have done in kernel used for android products (which probably stress PM the most) this is done and so I don't know of any shipping product that stresses PM that does not do this. May be someone else might. I personally would not be comfortable removing this without testing, but as I mentioned it is not a trivial thing to test correctly. However, I will let you and the other maintainers decide what's best here.

Quite frankly, I'm fairly sure most of this is untested anyway. There isn't a good way to test gr3d for example.

Generally I lean towards not merging any code that we don't know is needed. Merely because Android kernels do something doesn't mean it is necessary to do it (or even sane in some cases).

Now I understand that it might be difficult to test this, but that's all the more reason not to include the code. If we find that we have bugs that this can fix, then we can come up with tests to trigger it and validate that the fix actually fixes something.

In other words, I don't think it makes sense to implement mechanisms to recover from situations that we have no way of triggering in the first place.

Thierry
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 819 bytes
Desc: not available
URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20150720/0c490974/attachment.sig>

#9 lore raw

PATCH v3 2/19 memory: tegra: Add MC flush support

In reply to Thierry Reding
Quoted 34 lines · Thierry Reding

* PGP Signed by an unknown key

Quoted 31 lines · Peter De Schrijver
Quoted 29 lines · Thierry Reding
Quoted 1 line

Old Signed by an unknown key

Quoted 8 lines · Jon Hunter

The Tegra memory controller implements a flush feature to flush pending accesses and prevent further accesses from occurring. This feature is used when powering down IP blocks to ensure the IP block is in a good state. The flushes are organised by software groups and IP blocks are assigned in hardware to the different software groups. Add helper functions for requesting a handle to an MC flush for a given software group and enabling/disabling the MC flush itself.

Quoted 8 lines

This is based upon a change by Vince Hsu <[email protected]>.

---
 drivers/memory/tegra/mc.c | 110 ++++++++++++++++++++++++++++++++++++++++++++++
 drivers/memory/tegra/mc.h |   2 +
 include/soc/tegra/mc.h    |  34 ++++++++++++++
 3 files changed, 146 insertions(+)

Do we know if this is actually necessary? I remember having a discussion with Arnd Bergmann a while ago, and the Linux driver model kind of assumes that by the time a device is disabled all outstanding accesses will have stopped.

Do we have a way to determine that this even makes a difference? Can we trigger a case where not doing this would cause breakage and see that adding this fixes that particular issue?

Quoted 8 lines
Quoted 4 lines

Most likely it is. The memory controller can still be processing requests when the peripheral domain is powergated. This would mean the response cannot be delivered in that case. So we need to be sure there are no outstanding requests before shutting down the domain.

My point is that that's the driver's responsibility anyway, hence making the explicit flush unnecessary.

The peripheral driver doesn't know how long a request is queued in the memory controller. So it can't be responsible for ensuring this.

Cheers,

Peter.

#10 lore raw

PATCH v3 2/19 memory: tegra: Add MC flush support

Quoted 40 lines · Peter De Schrijver
Quoted 39 lines · Thierry Reding

* PGP Signed by an unknown key

Quoted 35 lines · Peter De Schrijver
Quoted 20 lines · Thierry Reding
Quoted 1 line

Old Signed by an unknown key

Quoted 8 lines · Jon Hunter

The Tegra memory controller implements a flush feature to flush pending accesses and prevent further accesses from occurring. This feature is used when powering down IP blocks to ensure the IP block is in a good state. The flushes are organised by software groups and IP blocks are assigned in hardware to the different software groups. Add helper functions for requesting a handle to an MC flush for a given software group and enabling/disabling the MC flush itself.

Quoted 8 lines

This is based upon a change by Vince Hsu <[email protected]>.

---
 drivers/memory/tegra/mc.c | 110 ++++++++++++++++++++++++++++++++++++++++++++++
 drivers/memory/tegra/mc.h |   2 +
 include/soc/tegra/mc.h    |  34 ++++++++++++++
 3 files changed, 146 insertions(+)
Quoted 9 lines

Do we know if this is actually necessary? I remember having a discussion with Arnd Bergmann a while ago, and the Linux driver model kind of assumes that by the time a device is disabled all outstanding accesses will have stopped.

Do we have a way to determine that this even makes a difference? Can we trigger a case where not doing this would cause breakage and see that adding this fixes that particular issue?

Most likely it is. The memory controller can still be processing requests when the peripheral domain is powergated. This would mean the response cannot be delivered in that case. So we need to be sure there are no outstanding requests before shutting down the domain.

Quoted 6 lines
Quoted 3 lines

My point is that that's the driver's responsibility anyway, hence making the explicit flush unnecessary.

The peripheral driver doesn't know how long a request is queued in the memory controller. So it can't be responsible for ensuring this.

Surely whenever the peripheral reports that the operation is done (be that via some DMA controller interrupt or syncpoint increment) the operation would have flushed from the memory controller. Drivers are already supposed to make sure this is the case when they are removed or suspended, so this would mean that we'd be adding all this code for no real gain.

It would also explain why we're currently not seeing any such problems.

Also note that I'm not saying no to this, I merely want to make sure that it's necessary, and in order to prove so we need tests to produce enough traffic for this to be exposed, and then we can also verify that the patches actually do what they're supposed to.

Thierry
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 819 bytes
Desc: not available
URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20150720/5cb9197d/attachment-0001.sig>

#11 lore raw

PATCH v3 2/19 memory: tegra: Add MC flush support

Quoted 50 lines · Thierry Reding

* PGP Signed by an unknown key

Quoted 40 lines · Peter De Schrijver
Quoted 39 lines · Thierry Reding
Quoted 1 line

Old Signed by an unknown key

Quoted 35 lines · Peter De Schrijver
Quoted 20 lines · Thierry Reding
Quoted 1 line

Old Signed by an unknown key

Quoted 8 lines · Jon Hunter

The Tegra memory controller implements a flush feature to flush pending accesses and prevent further accesses from occurring. This feature is used when powering down IP blocks to ensure the IP block is in a good state. The flushes are organised by software groups and IP blocks are assigned in hardware to the different software groups. Add helper functions for requesting a handle to an MC flush for a given software group and enabling/disabling the MC flush itself.

Quoted 8 lines

This is based upon a change by Vince Hsu <[email protected]>.

---
 drivers/memory/tegra/mc.c | 110 ++++++++++++++++++++++++++++++++++++++++++++++
 drivers/memory/tegra/mc.h |   2 +
 include/soc/tegra/mc.h    |  34 ++++++++++++++
 3 files changed, 146 insertions(+)
Quoted 9 lines

Do we know if this is actually necessary? I remember having a discussion with Arnd Bergmann a while ago, and the Linux driver model kind of assumes that by the time a device is disabled all outstanding accesses will have stopped.

Do we have a way to determine that this even makes a difference? Can we trigger a case where not doing this would cause breakage and see that adding this fixes that particular issue?

Most likely it is. The memory controller can still be processing requests when the peripheral domain is powergated. This would mean the response cannot be delivered in that case. So we need to be sure there are no outstanding requests before shutting down the domain.

Quoted 6 lines
Quoted 3 lines

My point is that that's the driver's responsibility anyway, hence making the explicit flush unnecessary.

The peripheral driver doesn't know how long a request is queued in the memory controller. So it can't be responsible for ensuring this.

Quoted 7 lines

Surely whenever the peripheral reports that the operation is done (be that via some DMA controller interrupt or syncpoint increment) the operation would have flushed from the memory controller. Drivers are already supposed to make sure this is the case when they are removed or suspended, so this would mean that we'd be adding all this code for no real gain.

That highly depends on how the peripheral is implemented. I'm not sure we can assume things work this way.

Quoted 2 lines

It would also explain why we're currently not seeing any such problems.

I don't think we are doing agressive enough powergating today to see any problems. That might also mean we're just lucky.

Cheers,

Peter.

author maintainer
#12 lore raw

PATCH v3 3/19 memory: tegra: add flush operation for Tegra30 memory clients

In reply to Jon Hunter

From: Vince Hsu <[email protected]>

This patch adds the hot reset register table and flush related callback functions for Tegra30.

[jonathanh at nvidia.com: Removed tegra_mc_ops and added
metastable_flush_reads.]

---
v3: removal of tegra_mc_ops

---
 drivers/memory/tegra/tegra30.c | 24 ++++++++++++++++++++++++
 1 file changed, 24 insertions(+)
drivers/memory/tegra/tegra30.c +24 -0
index 1abcd8f6f3ba..3b4987f39b52 100644
@@ -6,6 +6,7 @@
  * published by the Free Software Foundation.
  */
 
+#include <linux/device.h>
 #include <linux/of.h>
 #include <linux/mm.h>
 
@@ -936,6 +937,26 @@ static const struct tegra_smmu_swgroup tegra30_swgroups[] = {
 	{ .name = "isp",  .swgroup = TEGRA_SWGROUP_ISP,  .reg = 0x258 },
 };
 
+static struct tegra_mc_flush tegra30_mc_flush[] = {
+	{TEGRA_SWGROUP_AFI,        0x200, 0x204,  0},
+	{TEGRA_SWGROUP_AVPC,       0x200, 0x204,  1},
+	{TEGRA_SWGROUP_DC,         0x200, 0x204,  2},
+	{TEGRA_SWGROUP_DCB,        0x200, 0x204,  3},
+	{TEGRA_SWGROUP_EPP,        0x200, 0x204,  4},
+	{TEGRA_SWGROUP_G2,         0x200, 0x204,  5},
+	{TEGRA_SWGROUP_HC,         0x200, 0x204,  6},
+	{TEGRA_SWGROUP_HDA,        0x200, 0x204,  7},
+	{TEGRA_SWGROUP_ISP,        0x200, 0x204,  8},
+	{TEGRA_SWGROUP_MPCORE,     0x200, 0x204,  9},
+	{TEGRA_SWGROUP_MPCORELP,   0x200, 0x204, 10},
+	{TEGRA_SWGROUP_MPE,        0x200, 0x204, 11},
+	{TEGRA_SWGROUP_NV,         0x200, 0x204, 12},
+	{TEGRA_SWGROUP_NV2,        0x200, 0x204, 13},
+	{TEGRA_SWGROUP_PPCS,       0x200, 0x204, 14},
+	{TEGRA_SWGROUP_VDE,        0x200, 0x204, 16},
+	{TEGRA_SWGROUP_VI,         0x200, 0x204, 17},
+};
+
 static void tegra30_flush_dcache(struct page *page, unsigned long offset,
 				 size_t size)
 {
@@ -967,4 +988,7 @@ const struct tegra_mc_soc tegra30_mc_soc = {
 	.num_address_bits = 32,
 	.atom_size = 16,
 	.smmu = &tegra30_smmu_soc,
+	.flushes = tegra30_mc_flush,
+	.num_flushes = ARRAY_SIZE(tegra30_mc_flush),
+	.metastable_flush_reads = 0,
 };
Signature · 1 line
2.1.4
#13 lore raw

PATCH v3 3/19 memory: tegra: add flush operation for Tegra30 memory clients

Quoted 21 lines · Jon Hunter

From: Vince Hsu <[email protected]>

This patch adds the hot reset register table and flush related callback functions for Tegra30.

[jonathanh at nvidia.com: Removed tegra_mc_ops and added
metastable_flush_reads.]

---
v3: removal of tegra_mc_ops

---
 drivers/memory/tegra/tegra30.c | 24 ++++++++++++++++++++++++
 1 file changed, 24 insertions(+)
drivers/memory/tegra/tegra30.c +0 -0
index 1abcd8f6f3ba..3b4987f39b52 100644

drivers/memory/tegra/tegra30.c line 9

Quoted 4 lines
drivers/memory/tegra/tegra30.c +1 -0
@@ -6,6 +6,7 @@
  * published by the Free Software Foundation.
  */
 
+#include <linux/device.h>

What do you need this for?

drivers/memory/tegra/tegra30.c static const struct tegra_smmu_swgroup tegra30_swgroups[] = { · line 940

Quoted 8 lines
drivers/memory/tegra/tegra30.c +1 -0
 #include <linux/of.h>
 #include <linux/mm.h>
 
@@ -936,6 +937,26 @@ static const struct tegra_smmu_swgroup tegra30_swgroups[] = {
 	{ .name = "isp",  .swgroup = TEGRA_SWGROUP_ISP,  .reg = 0x258 },
 };
 
+static struct tegra_mc_flush tegra30_mc_flush[] = {

const

Quoted 7 lines
drivers/memory/tegra/tegra30.c +7 -0
+	{TEGRA_SWGROUP_AFI,        0x200, 0x204,  0},
+	{TEGRA_SWGROUP_AVPC,       0x200, 0x204,  1},
+	{TEGRA_SWGROUP_DC,         0x200, 0x204,  2},
+	{TEGRA_SWGROUP_DCB,        0x200, 0x204,  3},
+	{TEGRA_SWGROUP_EPP,        0x200, 0x204,  4},
+	{TEGRA_SWGROUP_G2,         0x200, 0x204,  5},
+	{TEGRA_SWGROUP_HC,         0x200, 0x204,  6},

drivers/memory/tegra/tegra30.c static const struct tegra_smmu_swgroup tegra30_swgroups[] = { · line 957

Quoted 10 lines
drivers/memory/tegra/tegra30.c +10 -0
+	{TEGRA_SWGROUP_HDA,        0x200, 0x204,  7},
+	{TEGRA_SWGROUP_ISP,        0x200, 0x204,  8},
+	{TEGRA_SWGROUP_MPCORE,     0x200, 0x204,  9},
+	{TEGRA_SWGROUP_MPCORELP,   0x200, 0x204, 10},
+	{TEGRA_SWGROUP_MPE,        0x200, 0x204, 11},
+	{TEGRA_SWGROUP_NV,         0x200, 0x204, 12},
+	{TEGRA_SWGROUP_NV2,        0x200, 0x204, 13},
+	{TEGRA_SWGROUP_PPCS,       0x200, 0x204, 14},
+	{TEGRA_SWGROUP_VDE,        0x200, 0x204, 16},
+	{TEGRA_SWGROUP_VI,         0x200, 0x204, 17},

Spaces around { and }, please.

Thierry
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 819 bytes
Desc: not available
URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20150717/c897e0ba/attachment.sig>

author maintainer
#14 lore raw

PATCH v3 3/19 memory: tegra: add flush operation for Tegra30 memory clients

Quoted 27 lines · Thierry Reding

* PGP Signed by an unknown key

Quoted 24 lines · Jon Hunter

From: Vince Hsu <[email protected]>

This patch adds the hot reset register table and flush related callback functions for Tegra30.

[jonathanh at nvidia.com: Removed tegra_mc_ops and added
metastable_flush_reads.]

---
v3: removal of tegra_mc_ops

---
 drivers/memory/tegra/tegra30.c | 24 ++++++++++++++++++++++++
 1 file changed, 24 insertions(+)
drivers/memory/tegra/tegra30.c +0 -0
index 1abcd8f6f3ba..3b4987f39b52 100644
@@ -6,6 +6,7 @@
  * published by the Free Software Foundation.
  */
 
Quoted 3 lines

drivers/memory/tegra/tegra30.c line 9

Quoted 1 line
drivers/memory/tegra/tegra30.c +1 -0
+#include <linux/device.h>

What do you need this for?

Looks like I forgot to remove this, when removing the tegra_mc_ops. Thanks, will remove.

Quoted 10 lines

drivers/memory/tegra/tegra30.c static const struct tegra_smmu_swgroup tegra30_swgroups[] = { · line 940

Quoted 8 lines
drivers/memory/tegra/tegra30.c +1 -0
 #include <linux/of.h>
 #include <linux/mm.h>
 
@@ -936,6 +937,26 @@ static const struct tegra_smmu_swgroup tegra30_swgroups[] = {
 	{ .name = "isp",  .swgroup = TEGRA_SWGROUP_ISP,  .reg = 0x258 },
 };
 
+static struct tegra_mc_flush tegra30_mc_flush[] = {

const

Yes, will fix.

Quoted 11 lines
Quoted 11 lines
drivers/memory/tegra/tegra30.c +11 -0
+	{TEGRA_SWGROUP_AFI,        0x200, 0x204,  0},
+	{TEGRA_SWGROUP_AVPC,       0x200, 0x204,  1},
+	{TEGRA_SWGROUP_DC,         0x200, 0x204,  2},
+	{TEGRA_SWGROUP_DCB,        0x200, 0x204,  3},
+	{TEGRA_SWGROUP_EPP,        0x200, 0x204,  4},
+	{TEGRA_SWGROUP_G2,         0x200, 0x204,  5},
+	{TEGRA_SWGROUP_HC,         0x200, 0x204,  6},
+	{TEGRA_SWGROUP_HDA,        0x200, 0x204,  7},
+	{TEGRA_SWGROUP_ISP,        0x200, 0x204,  8},
+	{TEGRA_SWGROUP_MPCORE,     0x200, 0x204,  9},
+	{TEGRA_SWGROUP_MPCORELP,   0x200, 0x204, 10},
Quoted 8 lines

drivers/memory/tegra/tegra30.c static const struct tegra_smmu_swgroup tegra30_swgroups[] = { · line 957

Quoted 6 lines
drivers/memory/tegra/tegra30.c +6 -0
+	{TEGRA_SWGROUP_MPE,        0x200, 0x204, 11},
+	{TEGRA_SWGROUP_NV,         0x200, 0x204, 12},
+	{TEGRA_SWGROUP_NV2,        0x200, 0x204, 13},
+	{TEGRA_SWGROUP_PPCS,       0x200, 0x204, 14},
+	{TEGRA_SWGROUP_VDE,        0x200, 0x204, 16},
+	{TEGRA_SWGROUP_VI,         0x200, 0x204, 17},

Spaces around { and }, please.

Ok.

Cheers
Jon

author maintainer
#15 lore raw

PATCH v3 4/19 memory: tegra: add flush operation for Tegra114 memory clients

In reply to Jon Hunter

From: Vince Hsu <[email protected]>

This patch adds the hot reset register table and flush related callback functions for Tegra114.

[jonathanh at nvidia.com: Removed tegra_mc_ops and added
metastable_flush_reads.]

---
 drivers/memory/tegra/tegra114.c | 21 +++++++++++++++++++++
 1 file changed, 21 insertions(+)
drivers/memory/tegra/tegra114.c +21 -0
index 9f579589e800..ba33c402ed68 100644
@@ -914,6 +914,24 @@ static const struct tegra_smmu_swgroup tegra114_swgroups[] = {
 	{ .name = "tsec",      .swgroup = TEGRA_SWGROUP_TSEC,      .reg = 0x294 },
 };
 
+static struct tegra_mc_flush tegra114_mc_flush[] = {
+	{TEGRA_SWGROUP_AVPC,       0x200, 0x204,  1},
+	{TEGRA_SWGROUP_DC,         0x200, 0x204,  2},
+	{TEGRA_SWGROUP_DCB,        0x200, 0x204,  3},
+	{TEGRA_SWGROUP_EPP,        0x200, 0x204,  4},
+	{TEGRA_SWGROUP_G2,         0x200, 0x204,  5},
+	{TEGRA_SWGROUP_HC,         0x200, 0x204,  6},
+	{TEGRA_SWGROUP_HDA,        0x200, 0x204,  7},
+	{TEGRA_SWGROUP_ISP,        0x200, 0x204,  8},
+	{TEGRA_SWGROUP_MPCORE,     0x200, 0x204,  9},
+	{TEGRA_SWGROUP_MPCORELP,   0x200, 0x204, 10},
+	{TEGRA_SWGROUP_MSENC,      0x200, 0x204, 11},
+	{TEGRA_SWGROUP_NV,         0x200, 0x204, 12},
+	{TEGRA_SWGROUP_PPCS,       0x200, 0x204, 14},
+	{TEGRA_SWGROUP_VDE,        0x200, 0x204, 16},
+	{TEGRA_SWGROUP_VI,         0x200, 0x204, 17},
+};
+
 static void tegra114_flush_dcache(struct page *page, unsigned long offset,
 				  size_t size)
 {
@@ -945,4 +963,7 @@ const struct tegra_mc_soc tegra114_mc_soc = {
 	.num_address_bits = 32,
 	.atom_size = 32,
 	.smmu = &tegra114_smmu_soc,
+	.flushes = tegra114_mc_flush,
+	.num_flushes = ARRAY_SIZE(tegra114_mc_flush),
+	.metastable_flush_reads = MC_FLUSH_METASTABLE_READS,
 };
Signature · 1 line
2.1.4
#16 lore raw

PATCH v3 4/19 memory: tegra: add flush operation for Tegra114 memory clients

Quoted 18 lines · Jon Hunter

From: Vince Hsu <[email protected]>

This patch adds the hot reset register table and flush related callback functions for Tegra114.

[jonathanh at nvidia.com: Removed tegra_mc_ops and added
metastable_flush_reads.]

---
 drivers/memory/tegra/tegra114.c | 21 +++++++++++++++++++++
 1 file changed, 21 insertions(+)
drivers/memory/tegra/tegra114.c +0 -0
index 9f579589e800..ba33c402ed68 100644

drivers/memory/tegra/tegra114.c static const struct tegra_smmu_swgroup tegra114_swgroups[] = { · line 917

Quoted 4 lines
drivers/memory/tegra/tegra114.c +1 -0
@@ -914,6 +914,24 @@ static const struct tegra_smmu_swgroup tegra114_swgroups[] = {
 	{ .name = "tsec",      .swgroup = TEGRA_SWGROUP_TSEC,      .reg = 0x294 },
 };
 
+static struct tegra_mc_flush tegra114_mc_flush[] = {

const

Quoted 6 lines
drivers/memory/tegra/tegra114.c +6 -0
+	{TEGRA_SWGROUP_AVPC,       0x200, 0x204,  1},
+	{TEGRA_SWGROUP_DC,         0x200, 0x204,  2},
+	{TEGRA_SWGROUP_DCB,        0x200, 0x204,  3},
+	{TEGRA_SWGROUP_EPP,        0x200, 0x204,  4},
+	{TEGRA_SWGROUP_G2,         0x200, 0x204,  5},
+	{TEGRA_SWGROUP_HC,         0x200, 0x204,  6},

drivers/memory/tegra/tegra114.c static const struct tegra_smmu_swgroup tegra114_swgroups[] = { · line 933

Quoted 10 lines
drivers/memory/tegra/tegra114.c +10 -0
+	{TEGRA_SWGROUP_HDA,        0x200, 0x204,  7},
+	{TEGRA_SWGROUP_ISP,        0x200, 0x204,  8},
+	{TEGRA_SWGROUP_MPCORE,     0x200, 0x204,  9},
+	{TEGRA_SWGROUP_MPCORELP,   0x200, 0x204, 10},
+	{TEGRA_SWGROUP_MSENC,      0x200, 0x204, 11},
+	{TEGRA_SWGROUP_NV,         0x200, 0x204, 12},
+	{TEGRA_SWGROUP_PPCS,       0x200, 0x204, 14},
+	{TEGRA_SWGROUP_VDE,        0x200, 0x204, 16},
+	{TEGRA_SWGROUP_VI,         0x200, 0x204, 17},
+};

And spaces after { and before }.

drivers/memory/tegra/tegra114.c const struct tegra_mc_soc tegra114_mc_soc = { · line 968

Quoted 11 lines
drivers/memory/tegra/tegra114.c +4 -0
+
 static void tegra114_flush_dcache(struct page *page, unsigned long offset,
 				  size_t size)
 {
@@ -945,4 +963,7 @@ const struct tegra_mc_soc tegra114_mc_soc = {
 	.num_address_bits = 32,
 	.atom_size = 32,
 	.smmu = &tegra114_smmu_soc,
+	.flushes = tegra114_mc_flush,
+	.num_flushes = ARRAY_SIZE(tegra114_mc_flush),
+	.metastable_flush_reads = MC_FLUSH_METASTABLE_READS,

I don't think it's useful to have this define. Just use the literal value here. Much of the purpose of having this per-SoC parameter is to provide the context that otherwise the macro symbol would provide.

Thierry
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 819 bytes
Desc: not available
URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20150717/397468ae/attachment.sig>

author maintainer
#17 lore raw

PATCH v3 5/19 memory: tegra: add flush operation for Tegra124 memory clients

In reply to Jon Hunter

From: Vince Hsu <[email protected]>

This patch adds the hot reset register table and flush related callback functions for Tegra124.

[jonathanh at nvidia.com: Removed tegra_mc_ops and added
metastable_flush_reads.]

---
 drivers/memory/tegra/tegra124.c | 31 +++++++++++++++++++++++++++++++
 1 file changed, 31 insertions(+)
drivers/memory/tegra/tegra124.c +31 -0
index 966e1557e6f4..a24993db14ed 100644
@@ -1002,6 +1002,34 @@ static const struct tegra_smmu_swgroup tegra124_swgroups[] = {
 };
 
 #ifdef CONFIG_ARCH_TEGRA_124_SOC
+
+static struct tegra_mc_flush tegra124_mc_flush[] = {
+	{TEGRA_SWGROUP_AFI,        0x200, 0x204,  0},
+	{TEGRA_SWGROUP_AVPC,       0x200, 0x204,  1},
+	{TEGRA_SWGROUP_DC,         0x200, 0x204,  2},
+	{TEGRA_SWGROUP_DCB,        0x200, 0x204,  3},
+	{TEGRA_SWGROUP_HC,         0x200, 0x204,  6},
+	{TEGRA_SWGROUP_HDA,        0x200, 0x204,  7},
+	{TEGRA_SWGROUP_ISP2,       0x200, 0x204,  8},
+	{TEGRA_SWGROUP_MPCORE,     0x200, 0x204,  9},
+	{TEGRA_SWGROUP_MPCORELP,   0x200, 0x204, 10},
+	{TEGRA_SWGROUP_MSENC,      0x200, 0x204, 11},
+	{TEGRA_SWGROUP_PPCS,       0x200, 0x204, 14},
+	{TEGRA_SWGROUP_SATA,       0x200, 0x204, 15},
+	{TEGRA_SWGROUP_VDE,        0x200, 0x204, 16},
+	{TEGRA_SWGROUP_VI,         0x200, 0x204, 17},
+	{TEGRA_SWGROUP_VIC,        0x200, 0x204, 18},
+	{TEGRA_SWGROUP_XUSB_HOST,  0x200, 0x204, 19},
+	{TEGRA_SWGROUP_XUSB_DEV,   0x200, 0x204, 20},
+	{TEGRA_SWGROUP_TSEC,       0x200, 0x204, 22},
+	{TEGRA_SWGROUP_SDMMC1A,    0x200, 0x204, 29},
+	{TEGRA_SWGROUP_SDMMC2A,    0x200, 0x204, 30},
+	{TEGRA_SWGROUP_SDMMC3A,    0x200, 0x204, 31},
+	{TEGRA_SWGROUP_SDMMC4A,    0x970, 0x974,  0},
+	{TEGRA_SWGROUP_ISP2B,      0x970, 0x974,  1},
+	{TEGRA_SWGROUP_GPU,        0x970, 0x974,  2},
+};
+
 static void tegra124_flush_dcache(struct page *page, unsigned long offset,
 				  size_t size)
 {
@@ -1035,6 +1063,9 @@ const struct tegra_mc_soc tegra124_mc_soc = {
 	.smmu = &tegra124_smmu_soc,
 	.emem_regs = tegra124_mc_emem_regs,
 	.num_emem_regs = ARRAY_SIZE(tegra124_mc_emem_regs),
+	.flushes = tegra124_mc_flush,
+	.num_flushes = ARRAY_SIZE(tegra124_mc_flush),
+	.metastable_flush_reads = MC_FLUSH_METASTABLE_READS,
 };
 #endif /* CONFIG_ARCH_TEGRA_124_SOC */
 
Signature · 1 line
2.1.4
#18 lore raw

PATCH v3 5/19 memory: tegra: add flush operation for Tegra124 memory clients

Quoted 5 lines · Jon Hunter

From: Vince Hsu <[email protected]>

This patch adds the hot reset register table and flush related callback functions for Tegra124.

Quoted 7 lines

[jonathanh at nvidia.com: Removed tegra_mc_ops and added
metastable_flush_reads.]

---
 drivers/memory/tegra/tegra124.c | 31 +++++++++++++++++++++++++++++++
 1 file changed, 31 insertions(+)

Same comments as for Tegra114.

Thierry
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 819 bytes
Desc: not available
URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20150717/7bccb043/attachment.sig>

author maintainer
#19 lore raw

PATCH v3 6/19 clk: tegra: remove TEGRA_PLL_USE_LOCK for PLLD/PLLD2

In reply to Jon Hunter

From: Vince Hsu <[email protected]>

Tegra114 has a HW bug that the PLLD/PLLD2 lock bit cannot be asserted when the DIS power domain is during up-powergating process but the clamp to this domain is not removed yet. That causes a timeout and aborts the power sequence, although the PLLD/PLLD2 has already locked. To remove the false alarm, we don't use the lock for PLLD/PLLD2. Just wait 1ms and treat the clocks as locked.

---
 drivers/clk/tegra/clk-tegra114.c | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)
drivers/clk/tegra/clk-tegra114.c +2 -4
index 8237d16b4075..2e5c20c7c088 100644
@@ -456,8 +456,7 @@ static struct tegra_clk_pll_params pll_d_params = {
 	.lock_delay = 1000,
 	.div_nmp = &pllp_nmp,
 	.freq_table = pll_d_freq_table,
-	.flags = TEGRA_PLL_HAS_CPCON | TEGRA_PLL_SET_LFCON |
-		 TEGRA_PLL_USE_LOCK,
+	.flags = TEGRA_PLL_HAS_CPCON | TEGRA_PLL_SET_LFCON,
 };
 
 static struct tegra_clk_pll_params pll_d2_params = {
@@ -474,8 +473,7 @@ static struct tegra_clk_pll_params pll_d2_params = {
 	.lock_delay = 1000,
 	.div_nmp = &pllp_nmp,
 	.freq_table = pll_d_freq_table,
-	.flags = TEGRA_PLL_HAS_CPCON | TEGRA_PLL_SET_LFCON |
-		 TEGRA_PLL_USE_LOCK,
+	.flags = TEGRA_PLL_HAS_CPCON | TEGRA_PLL_SET_LFCON,
 };
 
 static struct pdiv_map pllu_p[] = {
Signature · 1 line
2.1.4
#20 lore raw

PATCH v3 6/19 clk: tegra: remove TEGRA_PLL_USE_LOCK for PLLD/PLLD2

Quoted 4 lines · Jon Hunter

From: Vince Hsu <[email protected]>

Tegra114 has a HW bug that the PLLD/PLLD2 lock bit cannot be asserted when the DIS power domain is during up-powergating process but the clamp to this

I think there is missing 'off' in this sentence?

ie. ... 'the DIS power domain is off?during up-powergating process'

Also 'un-powergating sequence' would be nicer.

Quoted 38 lines

domain is not removed yet. That causes a timeout and aborts the power sequence, although the PLLD/PLLD2 has already locked. To remove the false alarm, we don't use the lock for PLLD/PLLD2. Just wait 1ms and treat the clocks as locked.

---
 drivers/clk/tegra/clk-tegra114.c | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)
drivers/clk/tegra/clk-tegra114.c +2 -4
index 8237d16b4075..2e5c20c7c088 100644
@@ -456,8 +456,7 @@ static struct tegra_clk_pll_params pll_d_params = {
 	.lock_delay = 1000,
 	.div_nmp = &pllp_nmp,
 	.freq_table = pll_d_freq_table,
-	.flags = TEGRA_PLL_HAS_CPCON | TEGRA_PLL_SET_LFCON |
-		 TEGRA_PLL_USE_LOCK,
+	.flags = TEGRA_PLL_HAS_CPCON | TEGRA_PLL_SET_LFCON,
 };
 
 static struct tegra_clk_pll_params pll_d2_params = {
@@ -474,8 +473,7 @@ static struct tegra_clk_pll_params pll_d2_params = {
 	.lock_delay = 1000,
 	.div_nmp = &pllp_nmp,
 	.freq_table = pll_d_freq_table,
-	.flags = TEGRA_PLL_HAS_CPCON | TEGRA_PLL_SET_LFCON |
-		 TEGRA_PLL_USE_LOCK,
+	.flags = TEGRA_PLL_HAS_CPCON | TEGRA_PLL_SET_LFCON,
 };
 
 static struct pdiv_map pllu_p[] = {
Signature · 1 line
2.1.4
author maintainer
#21 lore raw

PATCH v3 6/19 clk: tegra: remove TEGRA_PLL_USE_LOCK for PLLD/PLLD2

Quoted 11 lines · Peter De Schrijver
Quoted 4 lines · Jon Hunter

From: Vince Hsu <[email protected]>

Tegra114 has a HW bug that the PLLD/PLLD2 lock bit cannot be asserted when the DIS power domain is during up-powergating process but the clamp to this

I think there is missing 'off' in this sentence?

ie. ... 'the DIS power domain is off during up-powergating process'

Also 'un-powergating sequence' would be nicer.

Yes agree. I will re-word that.

Thanks
Jon

author maintainer
#22 lore raw

PATCH v3 6/19 clk: tegra: remove TEGRA_PLL_USE_LOCK for PLLD/PLLD2

Quoted 9 lines · Jon Hunter
Quoted 6 lines · Peter De Schrijver
Quoted 4 lines · Jon Hunter

From: Vince Hsu <[email protected]>

Tegra114 has a HW bug that the PLLD/PLLD2 lock bit cannot be asserted when the DIS power domain is during up-powergating process but the clamp to this

Quoted 7 lines
Quoted 5 lines

I think there is missing 'off' in this sentence?

ie. ... 'the DIS power domain is off during up-powergating process'

Also 'un-powergating sequence' would be nicer.

Yes agree. I will re-word that.

Updated version ...

Quoted 1 line

From d5cbecd4e97332cd8373b9c4893731eb8e063660 Mon Sep 17 00:00:00 2001

From: Vince Hsu <[email protected]>
Date: Wed, 11 Mar 2015 16:46:04 +0800
Subject: [PATCH 06/19] clk: tegra: remove TEGRA_PLL_USE_LOCK for PLLD/PLLD2

Tegra114 has a HW bug where the PLLD/PLLD2 lock bit cannot be asserted while turning on the Display power domain and before the clamp to this domain has been removed. This issue causes a timeout and aborts the power up sequence, even though the PLLD/PLLD2 has already locked. To avoid this, don't use the lock for PLLD/PLLD2, just wait 1ms and treat the clocks as locked.

[jonathanh at nvidia.com: Updated the changelog description]
---
 drivers/clk/tegra/clk-tegra114.c | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)
drivers/clk/tegra/clk-tegra114.c +2 -4
index 8237d16b4075..2e5c20c7c088 100644
@@ -456,8 +456,7 @@ static struct tegra_clk_pll_params pll_d_params = {
 	.lock_delay = 1000,
 	.div_nmp = &pllp_nmp,
 	.freq_table = pll_d_freq_table,
-	.flags = TEGRA_PLL_HAS_CPCON | TEGRA_PLL_SET_LFCON |
-		 TEGRA_PLL_USE_LOCK,
+	.flags = TEGRA_PLL_HAS_CPCON | TEGRA_PLL_SET_LFCON,
 };
 
 static struct tegra_clk_pll_params pll_d2_params = {
@@ -474,8 +473,7 @@ static struct tegra_clk_pll_params pll_d2_params = {
 	.lock_delay = 1000,
 	.div_nmp = &pllp_nmp,
 	.freq_table = pll_d_freq_table,
-	.flags = TEGRA_PLL_HAS_CPCON | TEGRA_PLL_SET_LFCON |
-		 TEGRA_PLL_USE_LOCK,
+	.flags = TEGRA_PLL_HAS_CPCON | TEGRA_PLL_SET_LFCON,
 };
 
 static struct pdiv_map pllu_p[] = {
Signature · 1 line
2.1.4
#23 lore raw

PATCH v3 6/19 clk: tegra: remove TEGRA_PLL_USE_LOCK for PLLD/PLLD2

Quoted 34 lines · Jon Hunter
Quoted 9 lines · Jon Hunter
Quoted 6 lines · Peter De Schrijver
Quoted 4 lines · Jon Hunter

From: Vince Hsu <[email protected]>

Tegra114 has a HW bug that the PLLD/PLLD2 lock bit cannot be asserted when the DIS power domain is during up-powergating process but the clamp to this

Quoted 7 lines
Quoted 5 lines

I think there is missing 'off' in this sentence?

ie. ... 'the DIS power domain is off during up-powergating process'

Also 'un-powergating sequence' would be nicer.

Yes agree. I will re-word that.

Updated version ...

From d5cbecd4e97332cd8373b9c4893731eb8e063660 Mon Sep 17 00:00:00 2001
From: Vince Hsu <[email protected]>
Date: Wed, 11 Mar 2015 16:46:04 +0800
Subject: [PATCH 06/19] clk: tegra: remove TEGRA_PLL_USE_LOCK for PLLD/PLLD2

Tegra114 has a HW bug where the PLLD/PLLD2 lock bit cannot be asserted while turning on the Display power domain and before the clamp to this domain has been removed. This issue causes a timeout and aborts the power up sequence, even though the PLLD/PLLD2 has already locked. To avoid this, don't use the lock for PLLD/PLLD2, just wait 1ms and treat the clocks as locked.

Quoted 3 lines
[jonathanh at nvidia.com: Updated the changelog description]
Quoted 31 lines
---
 drivers/clk/tegra/clk-tegra114.c | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)
drivers/clk/tegra/clk-tegra114.c +2 -4
index 8237d16b4075..2e5c20c7c088 100644
@@ -456,8 +456,7 @@ static struct tegra_clk_pll_params pll_d_params = {
 	.lock_delay = 1000,
 	.div_nmp = &pllp_nmp,
 	.freq_table = pll_d_freq_table,
-	.flags = TEGRA_PLL_HAS_CPCON | TEGRA_PLL_SET_LFCON |
-		 TEGRA_PLL_USE_LOCK,
+	.flags = TEGRA_PLL_HAS_CPCON | TEGRA_PLL_SET_LFCON,
 };
 
 static struct tegra_clk_pll_params pll_d2_params = {
@@ -474,8 +473,7 @@ static struct tegra_clk_pll_params pll_d2_params = {
 	.lock_delay = 1000,
 	.div_nmp = &pllp_nmp,
 	.freq_table = pll_d_freq_table,
-	.flags = TEGRA_PLL_HAS_CPCON | TEGRA_PLL_SET_LFCON |
-		 TEGRA_PLL_USE_LOCK,
+	.flags = TEGRA_PLL_HAS_CPCON | TEGRA_PLL_SET_LFCON,
 };
 
 static struct pdiv_map pllu_p[] = {
Signature · 1 line
2.1.4
author maintainer
#24 lore raw

PATCH v3 7/19 soc: tegra: pmc: Wait for powergate state to change

In reply to Jon Hunter

Currently, the function tegra_powergate_set() simply sets the desired powergate state but does not wait for the state to change. In some circumstances this can be desirable. However, in most cases we should wait for the state to change before proceeding. Therefore, add a parameter to tegra_powergate_set() to indicate whether we should wait for the state to change.

By adding this feature, we can also eliminate the polling loop from tegra30_boot_secondary().

---
 arch/arm/mach-tegra/platsmp.c | 18 ++++--------------
 drivers/soc/tegra/pmc.c       | 29 +++++++++++++++++++++++------
 include/soc/tegra/pmc.h       |  2 +-
 3 files changed, 28 insertions(+), 21 deletions(-)
arch/arm/mach-tegra/platsmp.c +4 -14
index b45086666648..13982b5936c0 100644
@@ -108,19 +108,9 @@ static int tegra30_boot_secondary(unsigned int cpu, struct task_struct *idle)
 	 * be un-gated by un-toggling the power gate register
 	 * manually.
 	 */
-	if (!tegra_pmc_cpu_is_powered(cpu)) {
-		ret = tegra_pmc_cpu_power_on(cpu);
-		if (ret)
-			return ret;
-
-		/* Wait for the power to come up. */
-		timeout = jiffies + msecs_to_jiffies(100);
-		while (!tegra_pmc_cpu_is_powered(cpu)) {
-			if (time_after(jiffies, timeout))
-				return -ETIMEDOUT;
-			udelay(10);
-		}
-	}
+	ret = tegra_pmc_cpu_power_on(cpu, true);
+	if (ret)
+		return ret;
 
 remove_clamps:
 	/* CPU partition is powered. Enable the CPU clock. */
@@ -162,7 +152,7 @@ static int tegra114_boot_secondary(unsigned int cpu, struct task_struct *idle)
 		 * also initial power state in flow controller. After that,
 		 * the CPU's power state is maintained by flow controller.
 		 */
-		ret = tegra_pmc_cpu_power_on(cpu);
+		ret = tegra_pmc_cpu_power_on(cpu, false);
 	}
 
 	return ret;
drivers/soc/tegra/pmc.c +23 -6
index 300f11e0c3bb..c0635bdd4ee3 100644
@@ -175,9 +175,11 @@ static void tegra_pmc_writel(u32 value, unsigned long offset)
  * @id: partition ID
  * @new_state: new state of the partition
  */
-static int tegra_powergate_set(int id, bool new_state)
+static int tegra_powergate_set(int id, bool new_state, bool wait)
 {
+	unsigned long timeout;
 	bool status;
+	int ret = 0;
 
 	mutex_lock(&pmc->powergates_lock);
 
@@ -190,9 +192,23 @@ static int tegra_powergate_set(int id, bool new_state)
 
 	tegra_pmc_writel(PWRGATE_TOGGLE_START | id, PWRGATE_TOGGLE);
 
+	if (wait) {
+		timeout = jiffies + msecs_to_jiffies(100);
+		ret = -ETIMEDOUT;
+
+		while (time_before(jiffies, timeout)) {
+			status = !!(tegra_pmc_readl(PWRGATE_STATUS) & BIT(id));
+			if (status == new_state) {
+				ret = 0;
+				break;
+			}
+			udelay(10);
+		}
+	}
+
 	mutex_unlock(&pmc->powergates_lock);
 
-	return 0;
+	return ret;
 }
 
 /**
@@ -204,7 +220,7 @@ int tegra_powergate_power_on(int id)
 	if (!pmc->soc || id < 0 || id >= pmc->soc->num_powergates)
 		return -EINVAL;
 
-	return tegra_powergate_set(id, true);
+	return tegra_powergate_set(id, true, true);
 }
 
 /**
@@ -216,7 +232,7 @@ int tegra_powergate_power_off(int id)
 	if (!pmc->soc || id < 0 || id >= pmc->soc->num_powergates)
 		return -EINVAL;
 
-	return tegra_powergate_set(id, false);
+	return tegra_powergate_set(id, false, true);
 }
 EXPORT_SYMBOL(tegra_powergate_power_off);
 
@@ -351,8 +367,9 @@ bool tegra_pmc_cpu_is_powered(int cpuid)
 /**
  * tegra_pmc_cpu_power_on() - power on CPU partition
  * @cpuid: CPU partition ID
+ * @wait:  Wait for CPU state to transition
  */
-int tegra_pmc_cpu_power_on(int cpuid)
+int tegra_pmc_cpu_power_on(int cpuid, bool wait)
 {
 	int id;
 
@@ -360,7 +377,7 @@ int tegra_pmc_cpu_power_on(int cpuid)
 	if (id < 0)
 		return id;
 
-	return tegra_powergate_set(id, true);
+	return tegra_powergate_set(id, true, wait);
 }
 
 /**
include/soc/tegra/pmc.h +1 -1
index d18efe402ff1..3a014c121399 100644
@@ -34,7 +34,7 @@ void tegra_pmc_enter_suspend_mode(enum tegra_suspend_mode mode);
 
 #ifdef CONFIG_SMP
 bool tegra_pmc_cpu_is_powered(int cpuid);
-int tegra_pmc_cpu_power_on(int cpuid);
+int tegra_pmc_cpu_power_on(int cpuid, bool wait);
 int tegra_pmc_cpu_remove_clamping(int cpuid);
 #endif /* CONFIG_SMP */
 
Signature · 1 line
2.1.4
#25 lore raw

PATCH v3 7/19 soc: tegra: pmc: Wait for powergate state to change

Quoted 58 lines · Jon Hunter

Currently, the function tegra_powergate_set() simply sets the desired powergate state but does not wait for the state to change. In some circumstances this can be desirable. However, in most cases we should wait for the state to change before proceeding. Therefore, add a parameter to tegra_powergate_set() to indicate whether we should wait for the state to change.

By adding this feature, we can also eliminate the polling loop from tegra30_boot_secondary().

---
 arch/arm/mach-tegra/platsmp.c | 18 ++++--------------
 drivers/soc/tegra/pmc.c       | 29 +++++++++++++++++++++++------
 include/soc/tegra/pmc.h       |  2 +-
 3 files changed, 28 insertions(+), 21 deletions(-)
arch/arm/mach-tegra/platsmp.c +4 -14
index b45086666648..13982b5936c0 100644
@@ -108,19 +108,9 @@ static int tegra30_boot_secondary(unsigned int cpu, struct task_struct *idle)
 	 * be un-gated by un-toggling the power gate register
 	 * manually.
 	 */
-	if (!tegra_pmc_cpu_is_powered(cpu)) {
-		ret = tegra_pmc_cpu_power_on(cpu);
-		if (ret)
-			return ret;
-
-		/* Wait for the power to come up. */
-		timeout = jiffies + msecs_to_jiffies(100);
-		while (!tegra_pmc_cpu_is_powered(cpu)) {
-			if (time_after(jiffies, timeout))
-				return -ETIMEDOUT;
-			udelay(10);
-		}
-	}
+	ret = tegra_pmc_cpu_power_on(cpu, true);
+	if (ret)
+		return ret;
 
 remove_clamps:
 	/* CPU partition is powered. Enable the CPU clock. */
@@ -162,7 +152,7 @@ static int tegra114_boot_secondary(unsigned int cpu, struct task_struct *idle)
 		 * also initial power state in flow controller. After that,
 		 * the CPU's power state is maintained by flow controller.
 		 */
-		ret = tegra_pmc_cpu_power_on(cpu);
+		ret = tegra_pmc_cpu_power_on(cpu, false);
 	}
 
 	return ret;
drivers/soc/tegra/pmc.c +0 -0
index 300f11e0c3bb..c0635bdd4ee3 100644

drivers/soc/tegra/pmc.c static void tegra_pmc_writel(u32 value, unsigned long offset) · line 176

Quoted 2 lines
drivers/soc/tegra/pmc.c +0 -0
@@ -175,9 +175,11 @@ static void tegra_pmc_writel(u32 value, unsigned long offset)
  * @id: partition ID
  * @new_state: new state of the partition

The comment here isn't updated.

drivers/soc/tegra/pmc.c static void tegra_pmc_writel(u32 value, unsigned long offset) · line 178

Quoted 3 lines
drivers/soc/tegra/pmc.c +1 -1
  */
-static int tegra_powergate_set(int id, bool new_state)
+static int tegra_powergate_set(int id, bool new_state, bool wait)

Can we please not chain boolean parameters, it makes the call sites impossible to parse for humans. Instead, can we simply leave tegra_powergate_set() as it is and add another function, such as tegra_powergate_set_sync() or tegra_powergate_set_and_wait(), to achieve the same? You may have to split out a tegra_powergate_set_unlocked() or similar to make sure you get to keep the lock across both operations:

	static int tegra_powergate_set_unlocked(int id, bool new_state)
	{
		...
	}

	static int tegra_powergate_set(int id, bool new_state)
	{
		int err;

		mutex_lock(&pmc->powergates_lock);
		err = tegra_powergate_set_unlocked(id, new_state);
		mutex_unlock(&pmc->powergates_lock);

		return err;
	}

	/*
	 * Must be called with pmc->powergates_lock mutex held.
	 */
	static int tegra_powergate_wait(int id, bool new_state)
	{
		...
	}

	static int tegra_powergate_set_and_wait(int id, bool new_state)
	{
		int err;

		mutex_lock(&pmc->powergates_lock);

		err = tegra_powergate_set_unlocked(id, new_state);
		if (err < 0)
			goto unlock;

		err = tegra_powergate_wait(id, new_state);
		if (err < 0)
			goto unlock;

	unlock:
		mutex_unlock(&pmc->powergates_lock);
		return err;
	}
Quoted 51 lines
drivers/soc/tegra/pmc.c +19 -3
 {
+	unsigned long timeout;
 	bool status;
+	int ret = 0;
 
 	mutex_lock(&pmc->powergates_lock);
 
@@ -190,9 +192,23 @@ static int tegra_powergate_set(int id, bool new_state)
 
 	tegra_pmc_writel(PWRGATE_TOGGLE_START | id, PWRGATE_TOGGLE);
 
+	if (wait) {
+		timeout = jiffies + msecs_to_jiffies(100);
+		ret = -ETIMEDOUT;
+
+		while (time_before(jiffies, timeout)) {
+			status = !!(tegra_pmc_readl(PWRGATE_STATUS) & BIT(id));
+			if (status == new_state) {
+				ret = 0;
+				break;
+			}
+			udelay(10);
+		}
+	}
+
 	mutex_unlock(&pmc->powergates_lock);
 
-	return 0;
+	return ret;
 }
 
 /**
@@ -204,7 +220,7 @@ int tegra_powergate_power_on(int id)
 	if (!pmc->soc || id < 0 || id >= pmc->soc->num_powergates)
 		return -EINVAL;
 
-	return tegra_powergate_set(id, true);
+	return tegra_powergate_set(id, true, true);
 }
 
 /**
@@ -216,7 +232,7 @@ int tegra_powergate_power_off(int id)
 	if (!pmc->soc || id < 0 || id >= pmc->soc->num_powergates)
 		return -EINVAL;
 
-	return tegra_powergate_set(id, false);
+	return tegra_powergate_set(id, false, true);
 }
 EXPORT_SYMBOL(tegra_powergate_power_off);
 

drivers/soc/tegra/pmc.c bool tegra_pmc_cpu_is_powered(int cpuid) · line 372

Quoted 7 lines
drivers/soc/tegra/pmc.c +2 -1
@@ -351,8 +367,9 @@ bool tegra_pmc_cpu_is_powered(int cpuid)
 /**
  * tegra_pmc_cpu_power_on() - power on CPU partition
  * @cpuid: CPU partition ID
+ * @wait:  Wait for CPU state to transition
  */
-int tegra_pmc_cpu_power_on(int cpuid)
+int tegra_pmc_cpu_power_on(int cpuid, bool wait)

This one is probably fine since it's the only boolean parameter so far. That said, I see that we call this exactly twice, so I wonder if there'd be any harm in having the second occurrence wait as well and hence get rid of the parameter.

Thierry
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 819 bytes
Desc: not available
URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20150717/90379f1b/attachment.sig>

author maintainer
#26 lore raw

PATCH v3 7/19 soc: tegra: pmc: Wait for powergate state to change

Quoted 170 lines · Thierry Reding

* PGP Signed by an unknown key

Quoted 58 lines · Jon Hunter

Currently, the function tegra_powergate_set() simply sets the desired powergate state but does not wait for the state to change. In some circumstances this can be desirable. However, in most cases we should wait for the state to change before proceeding. Therefore, add a parameter to tegra_powergate_set() to indicate whether we should wait for the state to change.

By adding this feature, we can also eliminate the polling loop from tegra30_boot_secondary().

---
 arch/arm/mach-tegra/platsmp.c | 18 ++++--------------
 drivers/soc/tegra/pmc.c       | 29 +++++++++++++++++++++++------
 include/soc/tegra/pmc.h       |  2 +-
 3 files changed, 28 insertions(+), 21 deletions(-)
arch/arm/mach-tegra/platsmp.c +4 -14
index b45086666648..13982b5936c0 100644
@@ -108,19 +108,9 @@ static int tegra30_boot_secondary(unsigned int cpu, struct task_struct *idle)
 	 * be un-gated by un-toggling the power gate register
 	 * manually.
 	 */
-	if (!tegra_pmc_cpu_is_powered(cpu)) {
-		ret = tegra_pmc_cpu_power_on(cpu);
-		if (ret)
-			return ret;
-
-		/* Wait for the power to come up. */
-		timeout = jiffies + msecs_to_jiffies(100);
-		while (!tegra_pmc_cpu_is_powered(cpu)) {
-			if (time_after(jiffies, timeout))
-				return -ETIMEDOUT;
-			udelay(10);
-		}
-	}
+	ret = tegra_pmc_cpu_power_on(cpu, true);
+	if (ret)
+		return ret;
 
 remove_clamps:
 	/* CPU partition is powered. Enable the CPU clock. */
@@ -162,7 +152,7 @@ static int tegra114_boot_secondary(unsigned int cpu, struct task_struct *idle)
 		 * also initial power state in flow controller. After that,
 		 * the CPU's power state is maintained by flow controller.
 		 */
-		ret = tegra_pmc_cpu_power_on(cpu);
+		ret = tegra_pmc_cpu_power_on(cpu, false);
 	}
 
 	return ret;
drivers/soc/tegra/pmc.c +0 -0
index 300f11e0c3bb..c0635bdd4ee3 100644

drivers/soc/tegra/pmc.c static void tegra_pmc_writel(u32 value, unsigned long offset) · line 176

Quoted 2 lines
drivers/soc/tegra/pmc.c +0 -0
@@ -175,9 +175,11 @@ static void tegra_pmc_writel(u32 value, unsigned long offset)
  * @id: partition ID
  * @new_state: new state of the partition

The comment here isn't updated.

drivers/soc/tegra/pmc.c static void tegra_pmc_writel(u32 value, unsigned long offset) · line 178

Quoted 3 lines
drivers/soc/tegra/pmc.c +1 -1
  */
-static int tegra_powergate_set(int id, bool new_state)
+static int tegra_powergate_set(int id, bool new_state, bool wait)

Can we please not chain boolean parameters, it makes the call sites impossible to parse for humans. Instead, can we simply leave tegra_powergate_set() as it is and add another function, such as tegra_powergate_set_sync() or tegra_powergate_set_and_wait(), to achieve the same? You may have to split out a tegra_powergate_set_unlocked() or similar to make sure you get to keep the lock across both operations:

	static int tegra_powergate_set_unlocked(int id, bool new_state)
	{
		...
	}

	static int tegra_powergate_set(int id, bool new_state)
	{
		int err;

		mutex_lock(&pmc->powergates_lock);
		err = tegra_powergate_set_unlocked(id, new_state);
		mutex_unlock(&pmc->powergates_lock);

		return err;
	}

	/*
	 * Must be called with pmc->powergates_lock mutex held.
	 */
	static int tegra_powergate_wait(int id, bool new_state)
	{
		...
	}

	static int tegra_powergate_set_and_wait(int id, bool new_state)
	{
		int err;

		mutex_lock(&pmc->powergates_lock);

		err = tegra_powergate_set_unlocked(id, new_state);
		if (err < 0)
			goto unlock;

		err = tegra_powergate_wait(id, new_state);
		if (err < 0)
			goto unlock;

	unlock:
		mutex_unlock(&pmc->powergates_lock);
		return err;
	}
Quoted 50 lines
drivers/soc/tegra/pmc.c +19 -3
 {
+	unsigned long timeout;
 	bool status;
+	int ret = 0;
 
 	mutex_lock(&pmc->powergates_lock);
 
@@ -190,9 +192,23 @@ static int tegra_powergate_set(int id, bool new_state)
 
 	tegra_pmc_writel(PWRGATE_TOGGLE_START | id, PWRGATE_TOGGLE);
 
+	if (wait) {
+		timeout = jiffies + msecs_to_jiffies(100);
+		ret = -ETIMEDOUT;
+
+		while (time_before(jiffies, timeout)) {
+			status = !!(tegra_pmc_readl(PWRGATE_STATUS) & BIT(id));
+			if (status == new_state) {
+				ret = 0;
+				break;
+			}
+			udelay(10);
+		}
+	}
+
 	mutex_unlock(&pmc->powergates_lock);
 
-	return 0;
+	return ret;
 }
 
 /**
@@ -204,7 +220,7 @@ int tegra_powergate_power_on(int id)
 	if (!pmc->soc || id < 0 || id >= pmc->soc->num_powergates)
 		return -EINVAL;
 
-	return tegra_powergate_set(id, true);
+	return tegra_powergate_set(id, true, true);
 }
 
 /**
@@ -216,7 +232,7 @@ int tegra_powergate_power_off(int id)
 	if (!pmc->soc || id < 0 || id >= pmc->soc->num_powergates)
 		return -EINVAL;
 
-	return tegra_powergate_set(id, false);
+	return tegra_powergate_set(id, false, true);
 }
 EXPORT_SYMBOL(tegra_powergate_power_off);
 
Quoted 13 lines

drivers/soc/tegra/pmc.c bool tegra_pmc_cpu_is_powered(int cpuid) · line 372

Quoted 8 lines
drivers/soc/tegra/pmc.c +2 -1
@@ -351,8 +367,9 @@ bool tegra_pmc_cpu_is_powered(int cpuid)
 /**
  * tegra_pmc_cpu_power_on() - power on CPU partition
  * @cpuid: CPU partition ID
+ * @wait:  Wait for CPU state to transition
  */
-int tegra_pmc_cpu_power_on(int cpuid)
+int tegra_pmc_cpu_power_on(int cpuid, bool wait)

This one is probably fine since it's the only boolean parameter so far. That said, I see that we call this exactly twice, so I wonder if there'd be any harm in having the second occurrence wait as well and hence get rid of the parameter.

I would agree and think we should drop the 2nd parameter and just always wait. This is only done at boot time and so would only add a little delay at that point.

Cheers
Jon

author maintainer
#27 lore raw

PATCH v3 8/19 soc: tegra: pmc: Clean-up PMC helper functions

In reply to Jon Hunter

The following clean-ups have been made to the PMC code:

1. Add a macro for determining the state of a PMC powergate
2. Use the readx_poll_timeout() function instead of implementing a local polling loop with a timeout.
3. Use a case-statement in the function that removes the powergate clamp instead of multiple if-statements to improve readability.

---
 drivers/soc/tegra/pmc.c | 72 ++++++++++++++++++++++++-------------------------
 1 file changed, 35 insertions(+), 37 deletions(-)
drivers/soc/tegra/pmc.c +35 -37
index c0635bdd4ee3..180d434deec5 100644
@@ -28,6 +28,7 @@
 #include <linux/export.h>
 #include <linux/init.h>
 #include <linux/io.h>
+#include <linux/iopoll.h>
 #include <linux/of.h>
 #include <linux/of_address.h>
 #include <linux/platform_device.h>
@@ -56,6 +57,8 @@
 #define  PWRGATE_TOGGLE_START		(1 << 8)
 
 #define REMOVE_CLAMPING			0x34
+#define  REMOVE_CLAMPING_VDEC		(1 << 3)
+#define  REMOVE_CLAMPING_PCIE		(1 << 4)
 
 #define PWRGATE_STATUS			0x38
 
@@ -101,6 +104,8 @@
 
 #define GPU_RG_CNTRL			0x2d4
 
+#define PMC_PWRGATE_STATE(val, id)	(!!(val & BIT(id)))
+
 struct tegra_pmc_soc {
 	unsigned int num_powergates;
 	const char *const *powergates;
@@ -177,15 +182,14 @@ static void tegra_pmc_writel(u32 value, unsigned long offset)
  */
 static int tegra_powergate_set(int id, bool new_state, bool wait)
 {
-	unsigned long timeout;
-	bool status;
 	int ret = 0;
+	u32 val;
 
 	mutex_lock(&pmc->powergates_lock);
 
-	status = tegra_pmc_readl(PWRGATE_STATUS) & (1 << id);
+	val = tegra_pmc_readl(PWRGATE_STATUS);
 
-	if (status == new_state) {
+	if (PMC_PWRGATE_STATE(val, id) == new_state) {
 		mutex_unlock(&pmc->powergates_lock);
 		return 0;
 	}
@@ -193,17 +197,9 @@ static int tegra_powergate_set(int id, bool new_state, bool wait)
 	tegra_pmc_writel(PWRGATE_TOGGLE_START | id, PWRGATE_TOGGLE);
 
 	if (wait) {
-		timeout = jiffies + msecs_to_jiffies(100);
-		ret = -ETIMEDOUT;
-
-		while (time_before(jiffies, timeout)) {
-			status = !!(tegra_pmc_readl(PWRGATE_STATUS) & BIT(id));
-			if (status == new_state) {
-				ret = 0;
-				break;
-			}
-			udelay(10);
-		}
+		ret = readx_poll_timeout(tegra_pmc_readl, PWRGATE_STATUS,
+				val, PMC_PWRGATE_STATE(val, id) == new_state,
+				10, 100000);
 	}
 
 	mutex_unlock(&pmc->powergates_lock);
@@ -242,13 +238,10 @@ EXPORT_SYMBOL(tegra_powergate_power_off);
  */
 int tegra_powergate_is_powered(int id)
 {
-	u32 status;
-
 	if (!pmc->soc || id < 0 || id >= pmc->soc->num_powergates)
 		return -EINVAL;
 
-	status = tegra_pmc_readl(PWRGATE_STATUS) & (1 << id);
-	return !!status;
+	return PMC_PWRGATE_STATE(tegra_pmc_readl(PWRGATE_STATUS), id);
 }
 
 /**
@@ -257,34 +250,39 @@ int tegra_powergate_is_powered(int id)
  */
 int tegra_powergate_remove_clamping(int id)
 {
-	u32 mask;
+	u32 val, reg;
 
 	if (!pmc->soc || id < 0 || id >= pmc->soc->num_powergates)
 		return -EINVAL;
 
 	/*
-	 * On Tegra124 and later, the clamps for the GPU are controlled by a
-	 * separate register (with different semantics).
+	 * In most cases the bit for removing the IO clamping is the same as
+	 * the powergate partition ID, however, this is not always the case.
+	 * Furthermore, on Tegra124 and later, the clamps for the GPU are
+	 * controlled by a separate register (with different semantics). The
+	 * following case-statement handles these exceptions.
 	 */
-	if (id == TEGRA_POWERGATE_3D) {
+	val = 1 << id;
+	reg = REMOVE_CLAMPING;
+
+	switch (id) {
+	case TEGRA_POWERGATE_3D:
 		if (pmc->soc->has_gpu_clamps) {
-			tegra_pmc_writel(0, GPU_RG_CNTRL);
-			return 0;
+			val = 0;
+			reg  = GPU_RG_CNTRL;
 		}
+		break;
+	case TEGRA_POWERGATE_VDEC:
+		val = REMOVE_CLAMPING_VDEC;
+		break;
+	case TEGRA_POWERGATE_PCIE:
+		val = REMOVE_CLAMPING_PCIE;
+		break;
+	default:
+		break;
 	}
 
-	/*
-	 * Tegra 2 has a bug where PCIE and VDE clamping masks are
-	 * swapped relatively to the partition ids
-	 */
-	if (id == TEGRA_POWERGATE_VDEC)
-		mask = (1 << TEGRA_POWERGATE_PCIE);
-	else if (id == TEGRA_POWERGATE_PCIE)
-		mask = (1 << TEGRA_POWERGATE_VDEC);
-	else
-		mask = (1 << id);
-
-	tegra_pmc_writel(mask, REMOVE_CLAMPING);
+	tegra_pmc_writel(val, reg);
 
 	return 0;
 }
Signature · 1 line
2.1.4
#28 lore raw

PATCH v3 8/19 soc: tegra: pmc: Clean-up PMC helper functions

Quoted 35 lines · Jon Hunter

The following clean-ups have been made to the PMC code:

1. Add a macro for determining the state of a PMC powergate
2. Use the readx_poll_timeout() function instead of implementing a local polling loop with a timeout.
3. Use a case-statement in the function that removes the powergate clamp instead of multiple if-statements to improve readability.

---
 drivers/soc/tegra/pmc.c | 72 ++++++++++++++++++++++++-------------------------
 1 file changed, 35 insertions(+), 37 deletions(-)
drivers/soc/tegra/pmc.c +3 -0
index c0635bdd4ee3..180d434deec5 100644
@@ -28,6 +28,7 @@
 #include <linux/export.h>
 #include <linux/init.h>
 #include <linux/io.h>
+#include <linux/iopoll.h>
 #include <linux/of.h>
 #include <linux/of_address.h>
 #include <linux/platform_device.h>
@@ -56,6 +57,8 @@
 #define  PWRGATE_TOGGLE_START		(1 << 8)
 
 #define REMOVE_CLAMPING			0x34
+#define  REMOVE_CLAMPING_VDEC		(1 << 3)
+#define  REMOVE_CLAMPING_PCIE		(1 << 4)
 
 #define PWRGATE_STATUS			0x38
 

drivers/soc/tegra/pmc.c line 107

Quoted 4 lines
drivers/soc/tegra/pmc.c +1 -0
@@ -101,6 +104,8 @@
 
 #define GPU_RG_CNTRL			0x2d4
 
+#define PMC_PWRGATE_STATE(val, id)	(!!(val & BIT(id)))

I find double-negations very difficult to read. ((value & BIT(id)) != 0) is clearer in my opinion. Also I'd suggest status or value as the first parameter name, for consistency with other parts of the driver.

Quoted 46 lines
drivers/soc/tegra/pmc.c +7 -15
+
 struct tegra_pmc_soc {
 	unsigned int num_powergates;
 	const char *const *powergates;
@@ -177,15 +182,14 @@ static void tegra_pmc_writel(u32 value, unsigned long offset)
  */
 static int tegra_powergate_set(int id, bool new_state, bool wait)
 {
-	unsigned long timeout;
-	bool status;
 	int ret = 0;
+	u32 val;
 
 	mutex_lock(&pmc->powergates_lock);
 
-	status = tegra_pmc_readl(PWRGATE_STATUS) & (1 << id);
+	val = tegra_pmc_readl(PWRGATE_STATUS);
 
-	if (status == new_state) {
+	if (PMC_PWRGATE_STATE(val, id) == new_state) {
 		mutex_unlock(&pmc->powergates_lock);
 		return 0;
 	}
@@ -193,17 +197,9 @@ static int tegra_powergate_set(int id, bool new_state, bool wait)
 	tegra_pmc_writel(PWRGATE_TOGGLE_START | id, PWRGATE_TOGGLE);
 
 	if (wait) {
-		timeout = jiffies + msecs_to_jiffies(100);
-		ret = -ETIMEDOUT;
-
-		while (time_before(jiffies, timeout)) {
-			status = !!(tegra_pmc_readl(PWRGATE_STATUS) & BIT(id));
-			if (status == new_state) {
-				ret = 0;
-				break;
-			}
-			udelay(10);
-		}
+		ret = readx_poll_timeout(tegra_pmc_readl, PWRGATE_STATUS,
+				val, PMC_PWRGATE_STATE(val, id) == new_state,
+				10, 100000);
 	}
 
 	mutex_unlock(&pmc->powergates_lock);
@@ -242,13 +238,10 @@ EXPORT_SYMBOL(tegra_powergate_power_off);
  */

drivers/soc/tegra/pmc.c EXPORT_SYMBOL(tegra_powergate_power_off); · line 244

Quoted 10 lines
drivers/soc/tegra/pmc.c +1 -4
 int tegra_powergate_is_powered(int id)
 {
-	u32 status;
-
 	if (!pmc->soc || id < 0 || id >= pmc->soc->num_powergates)
 		return -EINVAL;
 
-	status = tegra_pmc_readl(PWRGATE_STATUS) & (1 << id);
-	return !!status;
+	return PMC_PWRGATE_STATE(tegra_pmc_readl(PWRGATE_STATUS), id);

I'd prefer to keep the two steps here. As a general rule I try never to mix reading or writing a register with other code on the same line.

drivers/soc/tegra/pmc.c int tegra_powergate_is_powered(int id) · line 253

Quoted 9 lines
drivers/soc/tegra/pmc.c +1 -1
 }
 
 /**
@@ -257,34 +250,39 @@ int tegra_powergate_is_powered(int id)
  */
 int tegra_powergate_remove_clamping(int id)
 {
-	u32 mask;
+	u32 val, reg;

Side note: Please use value instead of val since that's the form used elsewhere in the driver. Also reg would be more appropriately called offset or similar because that's what it really is. It would also be more naturally an unsized type, such as unsigned int. But...

Quoted 39 lines
drivers/soc/tegra/pmc.c +21 -8
 	if (!pmc->soc || id < 0 || id >= pmc->soc->num_powergates)
 		return -EINVAL;
 
 	/*
-	 * On Tegra124 and later, the clamps for the GPU are controlled by a
-	 * separate register (with different semantics).
+	 * In most cases the bit for removing the IO clamping is the same as
+	 * the powergate partition ID, however, this is not always the case.
+	 * Furthermore, on Tegra124 and later, the clamps for the GPU are
+	 * controlled by a separate register (with different semantics). The
+	 * following case-statement handles these exceptions.
 	 */
-	if (id == TEGRA_POWERGATE_3D) {
+	val = 1 << id;
+	reg = REMOVE_CLAMPING;
+
+	switch (id) {
+	case TEGRA_POWERGATE_3D:
 		if (pmc->soc->has_gpu_clamps) {
-			tegra_pmc_writel(0, GPU_RG_CNTRL);
-			return 0;
+			val = 0;
+			reg  = GPU_RG_CNTRL;
 		}
+		break;
+	case TEGRA_POWERGATE_VDEC:
+		val = REMOVE_CLAMPING_VDEC;
+		break;
+	case TEGRA_POWERGATE_PCIE:
+		val = REMOVE_CLAMPING_PCIE;
+		break;
+	default:
+		break;
 	}
 
-	/*
-	 * Tegra 2 has a bug where PCIE and VDE clamping masks are
-	 * swapped relatively to the partition ids

drivers/soc/tegra/pmc.c int tegra_powergate_is_powered(int id) · line 285

Quoted 10 lines
drivers/soc/tegra/pmc.c +1 -9
-	 */
-	if (id == TEGRA_POWERGATE_VDEC)
-		mask = (1 << TEGRA_POWERGATE_PCIE);
-	else if (id == TEGRA_POWERGATE_PCIE)
-		mask = (1 << TEGRA_POWERGATE_VDEC);
-	else
-		mask = (1 << id);
-
-	tegra_pmc_writel(mask, REMOVE_CLAMPING);
+	tegra_pmc_writel(val, reg);

... to be honest, I think the previous code was more straightforward, so I'd prefer if you dropped this particular hunk.

Thierry
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 819 bytes
Desc: not available
URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20150717/a6a93632/attachment.sig>

author maintainer
#29 lore raw

PATCH v3 8/19 soc: tegra: pmc: Clean-up PMC helper functions

Quoted 158 lines · Thierry Reding

* PGP Signed by an unknown key

Quoted 35 lines · Jon Hunter

The following clean-ups have been made to the PMC code:

1. Add a macro for determining the state of a PMC powergate
2. Use the readx_poll_timeout() function instead of implementing a local polling loop with a timeout.
3. Use a case-statement in the function that removes the powergate clamp instead of multiple if-statements to improve readability.

---
 drivers/soc/tegra/pmc.c | 72 ++++++++++++++++++++++++-------------------------
 1 file changed, 35 insertions(+), 37 deletions(-)
drivers/soc/tegra/pmc.c +3 -0
index c0635bdd4ee3..180d434deec5 100644
@@ -28,6 +28,7 @@
 #include <linux/export.h>
 #include <linux/init.h>
 #include <linux/io.h>
+#include <linux/iopoll.h>
 #include <linux/of.h>
 #include <linux/of_address.h>
 #include <linux/platform_device.h>
@@ -56,6 +57,8 @@
 #define  PWRGATE_TOGGLE_START		(1 << 8)
 
 #define REMOVE_CLAMPING			0x34
+#define  REMOVE_CLAMPING_VDEC		(1 << 3)
+#define  REMOVE_CLAMPING_PCIE		(1 << 4)
 
 #define PWRGATE_STATUS			0x38
 

drivers/soc/tegra/pmc.c line 107

Quoted 4 lines
drivers/soc/tegra/pmc.c +1 -0
@@ -101,6 +104,8 @@
 
 #define GPU_RG_CNTRL			0x2d4
 
+#define PMC_PWRGATE_STATE(val, id)	(!!(val & BIT(id)))

I find double-negations very difficult to read. ((value & BIT(id)) != 0) is clearer in my opinion. Also I'd suggest status or value as the first parameter name, for consistency with other parts of the driver.

Quoted 46 lines
drivers/soc/tegra/pmc.c +7 -15
+
 struct tegra_pmc_soc {
 	unsigned int num_powergates;
 	const char *const *powergates;
@@ -177,15 +182,14 @@ static void tegra_pmc_writel(u32 value, unsigned long offset)
  */
 static int tegra_powergate_set(int id, bool new_state, bool wait)
 {
-	unsigned long timeout;
-	bool status;
 	int ret = 0;
+	u32 val;
 
 	mutex_lock(&pmc->powergates_lock);
 
-	status = tegra_pmc_readl(PWRGATE_STATUS) & (1 << id);
+	val = tegra_pmc_readl(PWRGATE_STATUS);
 
-	if (status == new_state) {
+	if (PMC_PWRGATE_STATE(val, id) == new_state) {
 		mutex_unlock(&pmc->powergates_lock);
 		return 0;
 	}
@@ -193,17 +197,9 @@ static int tegra_powergate_set(int id, bool new_state, bool wait)
 	tegra_pmc_writel(PWRGATE_TOGGLE_START | id, PWRGATE_TOGGLE);
 
 	if (wait) {
-		timeout = jiffies + msecs_to_jiffies(100);
-		ret = -ETIMEDOUT;
-
-		while (time_before(jiffies, timeout)) {
-			status = !!(tegra_pmc_readl(PWRGATE_STATUS) & BIT(id));
-			if (status == new_state) {
-				ret = 0;
-				break;
-			}
-			udelay(10);
-		}
+		ret = readx_poll_timeout(tegra_pmc_readl, PWRGATE_STATUS,
+				val, PMC_PWRGATE_STATE(val, id) == new_state,
+				10, 100000);
 	}
 
 	mutex_unlock(&pmc->powergates_lock);
@@ -242,13 +238,10 @@ EXPORT_SYMBOL(tegra_powergate_power_off);
  */

drivers/soc/tegra/pmc.c EXPORT_SYMBOL(tegra_powergate_power_off); · line 244

Quoted 10 lines
drivers/soc/tegra/pmc.c +1 -4
 int tegra_powergate_is_powered(int id)
 {
-	u32 status;
-
 	if (!pmc->soc || id < 0 || id >= pmc->soc->num_powergates)
 		return -EINVAL;
 
-	status = tegra_pmc_readl(PWRGATE_STATUS) & (1 << id);
-	return !!status;
+	return PMC_PWRGATE_STATE(tegra_pmc_readl(PWRGATE_STATUS), id);

I'd prefer to keep the two steps here. As a general rule I try never to mix reading or writing a register with other code on the same line.

drivers/soc/tegra/pmc.c int tegra_powergate_is_powered(int id) · line 253

Quoted 9 lines
drivers/soc/tegra/pmc.c +1 -1
 }
 
 /**
@@ -257,34 +250,39 @@ int tegra_powergate_is_powered(int id)
  */
 int tegra_powergate_remove_clamping(int id)
 {
-	u32 mask;
+	u32 val, reg;

Side note: Please use value instead of val since that's the form used elsewhere in the driver. Also reg would be more appropriately called offset or similar because that's what it really is. It would also be more naturally an unsized type, such as unsigned int. But...

Quoted 36 lines
drivers/soc/tegra/pmc.c +21 -5
 	if (!pmc->soc || id < 0 || id >= pmc->soc->num_powergates)
 		return -EINVAL;
 
 	/*
-	 * On Tegra124 and later, the clamps for the GPU are controlled by a
-	 * separate register (with different semantics).
+	 * In most cases the bit for removing the IO clamping is the same as
+	 * the powergate partition ID, however, this is not always the case.
+	 * Furthermore, on Tegra124 and later, the clamps for the GPU are
+	 * controlled by a separate register (with different semantics). The
+	 * following case-statement handles these exceptions.
 	 */
-	if (id == TEGRA_POWERGATE_3D) {
+	val = 1 << id;
+	reg = REMOVE_CLAMPING;
+
+	switch (id) {
+	case TEGRA_POWERGATE_3D:
 		if (pmc->soc->has_gpu_clamps) {
-			tegra_pmc_writel(0, GPU_RG_CNTRL);
-			return 0;
+			val = 0;
+			reg  = GPU_RG_CNTRL;
 		}
+		break;
+	case TEGRA_POWERGATE_VDEC:
+		val = REMOVE_CLAMPING_VDEC;
+		break;
+	case TEGRA_POWERGATE_PCIE:
+		val = REMOVE_CLAMPING_PCIE;
+		break;
+	default:
+		break;
 	}
 
Quoted 16 lines

drivers/soc/tegra/pmc.c int tegra_powergate_is_powered(int id) · line 285

Quoted 13 lines
drivers/soc/tegra/pmc.c +1 -12
-	/*
-	 * Tegra 2 has a bug where PCIE and VDE clamping masks are
-	 * swapped relatively to the partition ids
-	 */
-	if (id == TEGRA_POWERGATE_VDEC)
-		mask = (1 << TEGRA_POWERGATE_PCIE);
-	else if (id == TEGRA_POWERGATE_PCIE)
-		mask = (1 << TEGRA_POWERGATE_VDEC);
-	else
-		mask = (1 << id);
-
-	tegra_pmc_writel(mask, REMOVE_CLAMPING);
+	tegra_pmc_writel(val, reg);

... to be honest, I think the previous code was more straightforward, so I'd prefer if you dropped this particular hunk.

Ok, I will drop that part. Jon

author maintainer
#30 lore raw

PATCH v3 9/19 soc: tegra: pmc: Prepare for migrating to generic PM domains

In reply to Jon Hunter

When turning on and off power-domains it is necessary to enable specific clocks in order to de-assert/assert various reset signals to place the logic into a good state.

Currently, clocks required for turning on a power-domain are left enabled by the PMC driver because these clocks are also needed by the IP blocks to operated.

When migrating to the generic PM domain infrastructure, control of the powergates is abstracted from the drivers and therefore, it is desirable to disabled the clocks after turning on the power-domain and let the drivers enable the clocks it needs later. However, because this behaviour is different it is necessary to add a new API that drivers can use whether generic PM domains are used or not so that the behaviour is the same. Hence, add a new API that disables the clocks after turning on the power domain.

Similarly, for disabling a power-domain add a new API to abstract the management of the clocks so that drivers can be migrated to generic PM domains.

---
 drivers/soc/tegra/pmc.c | 23 +++++++++++++++++++++++
 include/soc/tegra/pmc.h | 30 ++++++++++++++++++++++++++++++
 2 files changed, 53 insertions(+)
drivers/soc/tegra/pmc.c +23 -0
index 180d434deec5..934653785bb7 100644
@@ -331,6 +331,29 @@ err_power:
 }
 EXPORT_SYMBOL(tegra_powergate_sequence_power_up);
 
+int tegra_powergate_power_off_legacy(int id, struct clk *clk,
+				     struct reset_control *rst)
+{
+	int ret;
+
+	ret = clk_prepare_enable(clk);
+	if (ret)
+		return ret;
+
+	usleep_range(10, 20);
+
+	reset_control_assert(rst);
+
+	usleep_range(10, 20);
+
+	clk_disable_unprepare(clk);
+
+	usleep_range(10, 20);
+
+	return tegra_powergate_power_off(id);
+}
+EXPORT_SYMBOL(tegra_powergate_power_off_legacy);
+
 #ifdef CONFIG_SMP
 /**
  * tegra_get_cpu_powergate_id() - convert from CPU ID to partition ID
include/soc/tegra/pmc.h +30 -0
index 3a014c121399..4ca91d39304d 100644
@@ -19,6 +19,8 @@
 #ifndef __SOC_TEGRA_PMC_H__
 #define __SOC_TEGRA_PMC_H__
 
+#include <linux/clk.h>
+#include <linux/delay.h>
 #include <linux/reboot.h>
 
 #include <soc/tegra/pm.h>
@@ -117,6 +119,22 @@ int tegra_powergate_remove_clamping(int id);
 int tegra_powergate_sequence_power_up(int id, struct clk *clk,
 				      struct reset_control *rst);
 
+static inline int tegra_powergate_power_on_legacy(int id, struct clk *clk,
+						  struct reset_control *rst)
+{
+	int err = tegra_powergate_sequence_power_up(id, clk, rst);
+
+	if (!err) {
+		usleep_range(10, 20);
+		clk_disable_unprepare(clk);
+	}
+
+	return err;
+}
+
+int tegra_powergate_power_off_legacy(int id, struct clk *clk,
+				     struct reset_control *rst);
+
 int tegra_io_rail_power_on(int id);
 int tegra_io_rail_power_off(int id);
 #else
@@ -155,6 +173,18 @@ static inline int tegra_io_rail_power_off(int id)
 {
 	return -ENOSYS;
 }
+
+static inline int tegra_powergate_power_on_legacy(int id, struct clk *clk,
+						  struct reset_control *rst)
+{
+	return -ENOTSUPP;
+}
+
+static inline int tegra_powergate_power_off_legacy(int id, struct clk *clk,
+						   struct reset_control *rst)
+{
+	return -ENOTSUPP;
+}
 #endif /* CONFIG_ARCH_TEGRA */
 
 #endif /* __SOC_TEGRA_PMC_H__ */
Signature · 1 line
2.1.4
author maintainer
#31 lore raw

PATCH v3 10/19 drm/tegra: dc: Prepare for generic PM domains

In reply to Jon Hunter

Add support to the tegra dc driver for generic PM domains. However, to ensure backward compatibility with older device tree blobs ensure that the driver can work with or without generic PM domains. In order to migrate to generic PM domain infrastructure the necessary changes are:

1. If the "power-domains" property is present in the DT device node then generic PM domains is supported and pm_runtime_enable() should be called for the device. Furthermore, the enabling and disabling of the power-domain is handled via calling pm_runtime_get/put, respectively.

2. To ensure that clocks are managed consistently when generic PM domains are used and are not used, drivers should be migrated to use the tegra_powergate_power_on_legacy() and tegra_powergate_power_off_legacy() functions instead of the current tegra_powergate_sequence_power_up() and tegra_powergate_power_off(). The purpose of the tegra_powergate_power_on_legacy() and tegra_powergate_power_off_legacy() APIs is to mimick the behaviour of the tegra generic power-domain code, such that if generic power domains are not supported the functionality is the same.

3. The main difference between the tegra_powergate_sequence_power_up() API and the tegra_powergate_power_on_legacy() is that the clock used to enable the powergate is not kept enabled when using the tegra_powergate_power_on_legacy() API. Therefore, drivers must enable the clocks they need after calling tegra_powergate_power_on_legacy() and disable these clocks before calling tegra_powergate_power_off_legacy().

Helper functions have been added to the dc driver for handling power on and off the power-domains with and without generic PM domain support.

---
 drivers/gpu/drm/tegra/dc.c | 94 +++++++++++++++++++++++++++++-----------------
 1 file changed, 59 insertions(+), 35 deletions(-)
drivers/gpu/drm/tegra/dc.c +59 -35
index a287e4fec865..a5b2475a7b9c 100644
@@ -10,6 +10,7 @@
 #include <linux/clk.h>
 #include <linux/debugfs.h>
 #include <linux/iommu.h>
+#include <linux/pm_runtime.h>
 #include <linux/reset.h>
 
 #include <soc/tegra/pmc.h>
@@ -1880,6 +1881,49 @@ static int tegra_dc_parse_dt(struct tegra_dc *dc)
 	return 0;
 }
 
+static void tegra_dc_power_off(struct tegra_dc *dc)
+{
+	if (!dc->soc->has_powergate)
+		reset_control_assert(dc->rst);
+
+	clk_disable_unprepare(dc->clk);
+
+	if (dc->soc->has_powergate) {
+		if (pm_runtime_enabled(dc->dev))
+			pm_runtime_put_sync(dc->dev);
+		else
+			tegra_powergate_power_off_legacy(dc->powergate,
+							 dc->clk, dc->rst);
+	}
+}
+
+static int tegra_dc_power_on(struct tegra_dc *dc)
+{
+	int err;
+
+	if (dc->soc->has_powergate) {
+		if (pm_runtime_enabled(dc->dev))
+			err = pm_runtime_get_sync(dc->dev);
+		else
+			err = tegra_powergate_power_on_legacy(dc->powergate,
+							      dc->clk, dc->rst);
+		if (err < 0) {
+			dev_err(dc->dev, "failed to power partition: %d\n",
+				err);
+			return err;
+		}
+	}
+
+	err = clk_prepare_enable(dc->clk);
+	if (err < 0)
+		dev_err(dc->dev, "failed to enable clock: %d\n", err);
+
+	if (!dc->soc->has_powergate)
+		reset_control_deassert(dc->rst);
+
+	return err;
+}
+
 static int tegra_dc_probe(struct platform_device *pdev)
 {
 	unsigned long flags = HOST1X_SYNCPT_CLIENT_MANAGED;
@@ -1917,35 +1961,6 @@ static int tegra_dc_probe(struct platform_device *pdev)
 		return PTR_ERR(dc->rst);
 	}
 
-	if (dc->soc->has_powergate) {
-		if (dc->pipe == 0)
-			dc->powergate = TEGRA_POWERGATE_DIS;
-		else
-			dc->powergate = TEGRA_POWERGATE_DISB;
-
-		err = tegra_powergate_sequence_power_up(dc->powergate, dc->clk,
-							dc->rst);
-		if (err < 0) {
-			dev_err(&pdev->dev, "failed to power partition: %d\n",
-				err);
-			return err;
-		}
-	} else {
-		err = clk_prepare_enable(dc->clk);
-		if (err < 0) {
-			dev_err(&pdev->dev, "failed to enable clock: %d\n",
-				err);
-			return err;
-		}
-
-		err = reset_control_deassert(dc->rst);
-		if (err < 0) {
-			dev_err(&pdev->dev, "failed to deassert reset: %d\n",
-				err);
-			return err;
-		}
-	}
-
 	regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 	dc->regs = devm_ioremap_resource(&pdev->dev, regs);
 	if (IS_ERR(dc->regs))
@@ -1961,6 +1976,20 @@ static int tegra_dc_probe(struct platform_device *pdev)
 	dc->client.ops = &dc_client_ops;
 	dc->client.dev = &pdev->dev;
 
+	if (dc->soc->has_powergate) {
+		if (dc->pipe == 0)
+			dc->powergate = TEGRA_POWERGATE_DIS;
+		else
+			dc->powergate = TEGRA_POWERGATE_DISB;
+
+		if (of_property_read_bool(pdev->dev.of_node, "power-domains"))
+			pm_runtime_enable(&pdev->dev);
+	}
+
+	err = tegra_dc_power_on(dc);
+	if (err < 0)
+		return err;
+
 	err = tegra_dc_rgb_probe(dc);
 	if (err < 0 && err != -ENODEV) {
 		dev_err(&pdev->dev, "failed to probe RGB output: %d\n", err);
@@ -2003,12 +2032,7 @@ static int tegra_dc_remove(struct platform_device *pdev)
 		return err;
 	}
 
-	reset_control_assert(dc->rst);
-
-	if (dc->soc->has_powergate)
-		tegra_powergate_power_off(dc->powergate);
-
-	clk_disable_unprepare(dc->clk);
+	tegra_dc_power_off(dc);
 
 	return 0;
 }
Signature · 1 line
2.1.4
#32 lore raw

PATCH v3 10/19 drm/tegra: dc: Prepare for generic PM domains

Quoted 10 lines · Jon Hunter

Add support to the tegra dc driver for generic PM domains. However, to ensure backward compatibility with older device tree blobs ensure that the driver can work with or without generic PM domains. In order to migrate to generic PM domain infrastructure the necessary changes are:

1. If the "power-domains" property is present in the DT device node then generic PM domains is supported and pm_runtime_enable() should be called for the device. Furthermore, the enabling and disabling of the power-domain is handled via calling pm_runtime_get/put, respectively.

The device could be PM runtime enabled even if no power-domains property is set. Couldn't we check something more direct, like for example if the dev->pm_domain is non-NULL?

Perhaps it'd be worth converting the driver to use runtime PM first, and move all the powergate and clock handling into suspend/resume functions, and then we can conditionalize whether or not we call the legacy API iff dev->pm_domain == NULL?

Quoted 10 lines

2. To ensure that clocks are managed consistently when generic PM domains are used and are not used, drivers should be migrated to use the tegra_powergate_power_on_legacy() and tegra_powergate_power_off_legacy() functions instead of the current tegra_powergate_sequence_power_up() and tegra_powergate_power_off(). The purpose of the tegra_powergate_power_on_legacy() and tegra_powergate_power_off_legacy() APIs is to mimick the behaviour of the tegra generic power-domain code, such that if generic power domains are not supported the functionality is the same.

Quoted 7 lines

3. The main difference between the tegra_powergate_sequence_power_up() API and the tegra_powergate_power_on_legacy() is that the clock used to enable the powergate is not kept enabled when using the tegra_powergate_power_on_legacy() API. Therefore, drivers must enable the clocks they need after calling tegra_powergate_power_on_legacy() and disable these clocks before calling tegra_powergate_power_off_legacy().

This seems like it should go into the previous patch. I'm not sure I understand why this is necessary. Wouldn't it be easier to update the drivers to properly cope with this? We'll need to move them to runtime PM at some point anyway, so if we do that first, we should be able to hide all these details within the driver's suspend/resume functions but without the need for the API churn here.

Thierry
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 819 bytes
Desc: not available
URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20150717/0275a247/attachment.sig>

author maintainer
#33 lore raw

PATCH v3 10/19 drm/tegra: dc: Prepare for generic PM domains

Quoted 9 lines · Thierry Reding

* PGP Signed by an unknown key

Quoted 6 lines · Jon Hunter

Add support to the tegra dc driver for generic PM domains. However, to ensure backward compatibility with older device tree blobs ensure that the driver can work with or without generic PM domains. In order to migrate to generic PM domain infrastructure the necessary changes are:

Quoted 8 lines
Quoted 4 lines

1. If the "power-domains" property is present in the DT device node then generic PM domains is supported and pm_runtime_enable() should be called for the device. Furthermore, the enabling and disabling of the power-domain is handled via calling pm_runtime_get/put, respectively.

The device could be PM runtime enabled even if no power-domains property is set. Couldn't we check something more direct, like for example if the dev->pm_domain is non-NULL?

Yes could do that instead.

Quoted 4 lines

Perhaps it'd be worth converting the driver to use runtime PM first, and move all the powergate and clock handling into suspend/resume functions, and then we can conditionalize whether or not we call the legacy API iff dev->pm_domain == NULL?

May be that would be a cleaner transition than trying to do it all in one go.

Quoted 18 lines
Quoted 10 lines

2. To ensure that clocks are managed consistently when generic PM domains are used and are not used, drivers should be migrated to use the tegra_powergate_power_on_legacy() and tegra_powergate_power_off_legacy() functions instead of the current tegra_powergate_sequence_power_up() and tegra_powergate_power_off(). The purpose of the tegra_powergate_power_on_legacy() and tegra_powergate_power_off_legacy() APIs is to mimick the behaviour of the tegra generic power-domain code, such that if generic power domains are not supported the functionality is the same.

Quoted 7 lines

3. The main difference between the tegra_powergate_sequence_power_up() API and the tegra_powergate_power_on_legacy() is that the clock used to enable the powergate is not kept enabled when using the tegra_powergate_power_on_legacy() API. Therefore, drivers must enable the clocks they need after calling tegra_powergate_power_on_legacy() and disable these clocks before calling tegra_powergate_power_off_legacy().

Quoted 6 lines

This seems like it should go into the previous patch. I'm not sure I understand why this is necessary. Wouldn't it be easier to update the drivers to properly cope with this? We'll need to move them to runtime PM at some point anyway, so if we do that first, we should be able to hide all these details within the driver's suspend/resume functions but without the need for the API churn here.

I will take a look at that. I was trying to get the clock handling in the driver to be consistent when generic power domains are used and when they are not. However, may be that is not a big deal.

Jon

#34 lore raw

PATCH v3 10/19 drm/tegra: dc: Prepare for generic PM domains

Quoted 22 lines · Jon Hunter
Quoted 9 lines · Thierry Reding

* PGP Signed by an unknown key

Quoted 6 lines · Jon Hunter

Add support to the tegra dc driver for generic PM domains. However, to ensure backward compatibility with older device tree blobs ensure that the driver can work with or without generic PM domains. In order to migrate to generic PM domain infrastructure the necessary changes are:

Quoted 8 lines
Quoted 4 lines

1. If the "power-domains" property is present in the DT device node then generic PM domains is supported and pm_runtime_enable() should be called for the device. Furthermore, the enabling and disabling of the power-domain is handled via calling pm_runtime_get/put, respectively.

The device could be PM runtime enabled even if no power-domains property is set. Couldn't we check something more direct, like for example if the dev->pm_domain is non-NULL?

Yes could do that instead.

Quoted 7 lines
Quoted 4 lines

Perhaps it'd be worth converting the driver to use runtime PM first, and move all the powergate and clock handling into suspend/resume functions, and then we can conditionalize whether or not we call the legacy API iff dev->pm_domain == NULL?

May be that would be a cleaner transition than trying to do it all in one go.

I have a couple of patches in my tree to do this for DRM as part of an effort to restore DPMS. It's fairly tricky to get right in DRM and requires all sorts of changes to the driver.

Thierry
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 819 bytes
Desc: not available
URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20150728/f7735d82/attachment.sig>

author maintainer
#35 lore raw

PATCH v3 10/19 drm/tegra: dc: Prepare for generic PM domains

Quoted 7 lines · Thierry Reding
Quoted 2 lines · Jon Hunter

May be that would be a cleaner transition than trying to do it all in one go.

I have a couple of patches in my tree to do this for DRM as part of an effort to restore DPMS. It's fairly tricky to get right in DRM and requires all sorts of changes to the driver.

Hmmm ... I was just thinking about moving what is currently there in the pm-runtime helpers ...

drivers/gpu/drm/tegra/dc.c +59 -23
index a287e4fec865..0f1dc01215b1 100644
@@ -10,6 +10,7 @@
 #include <linux/clk.h>
 #include <linux/debugfs.h>
 #include <linux/iommu.h>
+#include <linux/pm_runtime.h>
 #include <linux/reset.h>
 
 #include <soc/tegra/pmc.h>
@@ -1922,29 +1923,12 @@ static int tegra_dc_probe(struct platform_device *pdev)
 			dc->powergate = TEGRA_POWERGATE_DIS;
 		else
 			dc->powergate = TEGRA_POWERGATE_DISB;
+	}
 
-		err = tegra_powergate_sequence_power_up(dc->powergate, dc->clk,
-							dc->rst);
-		if (err < 0) {
-			dev_err(&pdev->dev, "failed to power partition: %d\n",
-				err);
-			return err;
-		}
-	} else {
-		err = clk_prepare_enable(dc->clk);
-		if (err < 0) {
-			dev_err(&pdev->dev, "failed to enable clock: %d\n",
-				err);
-			return err;
-		}
+	platform_set_drvdata(pdev, dc);
 
-		err = reset_control_deassert(dc->rst);
-		if (err < 0) {
-			dev_err(&pdev->dev, "failed to deassert reset: %d\n",
-				err);
-			return err;
-		}
-	}
+	pm_runtime_enable(&pdev->dev);
+	pm_runtime_get_sync(&pdev->dev);
 
 	regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 	dc->regs = devm_ioremap_resource(&pdev->dev, regs);
@@ -1978,8 +1962,6 @@ static int tegra_dc_probe(struct platform_device *pdev)
 	if (!dc->syncpt)
 		dev_warn(&pdev->dev, "failed to allocate syncpoint\n");
 
-	platform_set_drvdata(pdev, dc);
-
 	return 0;
 }
 
@@ -2003,6 +1985,17 @@ static int tegra_dc_remove(struct platform_device *pdev)
 		return err;
 	}
 
+	pm_runtime_put(&pdev->dev);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int tegra_dc_runtime_suspend(struct device *dev)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct tegra_dc *dc = platform_get_drvdata(pdev);
+
 	reset_control_assert(dc->rst);
 
 	if (dc->soc->has_powergate)
@@ -2013,11 +2006,54 @@ static int tegra_dc_remove(struct platform_device *pdev)
 	return 0;
 }
 
+static int tegra_dc_runtime_resume(struct device *dev)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct tegra_dc *dc = platform_get_drvdata(pdev);
+	int err;
+
+	if (dc->soc->has_powergate) {
+		err = tegra_powergate_sequence_power_up(dc->powergate, dc->clk,
+							dc->rst);
+		if (err < 0) {
+			dev_err(&pdev->dev, "failed to power partition: %d\n",
+				err);
+			return err;
+		}
+	} else {
+		err = clk_prepare_enable(dc->clk);
+		if (err < 0) {
+			dev_err(&pdev->dev, "failed to enable clock: %d\n",
+				err);
+			return err;
+		}
+
+		err = reset_control_deassert(dc->rst);
+		if (err < 0) {
+			dev_err(&pdev->dev, "failed to deassert reset: %d\n",
+				err);
+			return err;
+		}
+	}
+
+	return 0;
+}
+
+static struct dev_pm_ops tegra_dc_pm_ops = {
+	SET_RUNTIME_PM_OPS(tegra_dc_runtime_suspend,
+			   tegra_dc_runtime_resume, NULL)
+};
+#define TEGRA_DC_PM_OPS (&tegra_dc_pm_ops)
+#else
+#define TEGRA_DC_PM_OPS NULL
+#endif /* CONFIG_PM */
+
 struct platform_driver tegra_dc_driver = {
 	.driver = {
 		.name = "tegra-dc",
 		.owner = THIS_MODULE,
 		.of_match_table = tegra_dc_of_match,
+		.pm = TEGRA_DC_PM_OPS,
 	},
 	.probe = tegra_dc_probe,
 	.remove = tegra_dc_remove,
Signature · 1 line
2.1.4
author maintainer
#36 lore raw

PATCH v3 11/19 PCI: tegra: Add support for generic PM domains

In reply to Jon Hunter

Add support to the tegra PCI driver for generic PM domains. However, to ensure backward compatibility with older device tree blobs ensure that the driver can work with or without generic PM domains. In order to migrate to generic PM domain infrastructure the necessary changes are:

1. If the "power-domains" property is present in the DT device node then generic PM domains is supported and pm_runtime_enable() should be called for the device. Furthermore, the enabling and disabling of the power-domain is handled via calling pm_runtime_get/put, respectively.

2. To ensure that clocks are managed consistently when generic PM domains are used and are not used, drivers should be migrated to use the tegra_powergate_power_on_legacy() and tegra_powergate_power_off_legacy() functions instead of the current tegra_powergate_sequence_power_up() and tegra_powergate_power_off(). The purpose of the tegra_powergate_power_on_legacy() and tegra_powergate_power_off_legacy() APIs is to mimick the behaviour of the tegra generic power-domain code, such that if generic power domains are not supported the functionality is the same.

3. The main difference between the tegra_powergate_sequence_power_up() API and the tegra_powergate_power_on_legacy() is that the clock used to enable the powergate is not kept enabled when using the tegra_powergate_power_on_legacy() API. Therefore, drivers must enable the clocks they need after calling tegra_powergate_power_on_legacy() and disable these clocks before calling tegra_powergate_power_off_legacy().

The helper functions for handling the powering on and off of the PCI controller have been updated to support generic PM domains. The tegra_pcie_power_off() was missing code to disable the clocks enabled in the tegra_pcie_power_on() function and so this has been added.

---
 drivers/pci/host/pci-tegra.c | 49 +++++++++++++++++++++++++++++++++-----------
 1 file changed, 37 insertions(+), 12 deletions(-)
drivers/pci/host/pci-tegra.c +37 -12
index 10c05718dbfd..acd1f311eee5 100644
@@ -40,6 +40,7 @@
 #include <linux/pci.h>
 #include <linux/phy/phy.h>
 #include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
 #include <linux/reset.h>
 #include <linux/sizes.h>
 #include <linux/slab.h>
@@ -908,19 +909,32 @@ static int tegra_pcie_enable_controller(struct tegra_pcie *pcie)
 
 static void tegra_pcie_power_off(struct tegra_pcie *pcie)
 {
+	const struct tegra_pcie_soc_data *soc = pcie->soc_data;
 	int err;
 
-	/* TODO: disable and unprepare clocks? */
-
 	err = phy_power_off(pcie->phy);
 	if (err < 0)
 		dev_warn(pcie->dev, "failed to power off PHY: %d\n", err);
 
 	reset_control_assert(pcie->pcie_xrst);
 	reset_control_assert(pcie->afi_rst);
-	reset_control_assert(pcie->pex_rst);
 
-	tegra_powergate_power_off(TEGRA_POWERGATE_PCIE);
+	clk_disable_unprepare(pcie->pll_e);
+	if (soc->has_cml_clk)
+		clk_disable_unprepare(pcie->cml_clk);
+	clk_disable_unprepare(pcie->afi_clk);
+	clk_disable_unprepare(pcie->pex_clk);
+
+	if (pm_runtime_enabled(pcie->dev)) {
+		err = pm_runtime_put_sync(pcie->dev);
+	} else {
+		err = tegra_powergate_power_off_legacy(TEGRA_POWERGATE_PCIE,
+						       pcie->pex_clk,
+						       pcie->pex_rst);
+	}
+
+	if (err < 0)
+		dev_warn(pcie->dev, "powergate power-down failed: %d\n", err);
 
 	err = regulator_bulk_disable(pcie->num_supplies, pcie->supplies);
 	if (err < 0)
@@ -934,20 +948,28 @@ static int tegra_pcie_power_on(struct tegra_pcie *pcie)
 
 	reset_control_assert(pcie->pcie_xrst);
 	reset_control_assert(pcie->afi_rst);
-	reset_control_assert(pcie->pex_rst);
-
-	tegra_powergate_power_off(TEGRA_POWERGATE_PCIE);
 
 	/* enable regulators */
 	err = regulator_bulk_enable(pcie->num_supplies, pcie->supplies);
 	if (err < 0)
 		dev_err(pcie->dev, "failed to enable regulators: %d\n", err);
 
-	err = tegra_powergate_sequence_power_up(TEGRA_POWERGATE_PCIE,
-						pcie->pex_clk,
-						pcie->pex_rst);
-	if (err) {
-		dev_err(pcie->dev, "powerup sequence failed: %d\n", err);
+	if (pm_runtime_enabled(pcie->dev)) {
+		err = pm_runtime_get_sync(pcie->dev);
+	} else {
+		err = tegra_powergate_power_on_legacy(TEGRA_POWERGATE_PCIE,
+						      pcie->pex_clk,
+						      pcie->pex_rst);
+	}
+
+	if (err < 0) {
+		dev_err(pcie->dev, "powergate power-up failed: %d\n", err);
+		return err;
+	}
+
+	err = clk_prepare_enable(pcie->pex_clk);
+	if (err < 0) {
+		dev_err(pcie->dev, "failed to enable PEX clock: %d\n", err);
 		return err;
 	}
 
@@ -2001,6 +2023,9 @@ static int tegra_pcie_probe(struct platform_device *pdev)
 
 	pcibios_min_mem = 0;
 
+	if (of_property_read_bool(pdev->dev.of_node, "power-domains"))
+		pm_runtime_enable(&pdev->dev);
+
 	err = tegra_pcie_get_resources(pcie);
 	if (err < 0) {
 		dev_err(&pdev->dev, "failed to request resources: %d\n", err);
Signature · 1 line
2.1.4
#37 lore raw

PATCH v3 11/19 PCI: tegra: Add support for generic PM domains

Quoted 21 lines · Jon Hunter

Add support to the tegra PCI driver for generic PM domains. However, to ensure backward compatibility with older device tree blobs ensure that the driver can work with or without generic PM domains. In order to migrate to generic PM domain infrastructure the necessary changes are:

1. If the "power-domains" property is present in the DT device node then generic PM domains is supported and pm_runtime_enable() should be called for the device. Furthermore, the enabling and disabling of the power-domain is handled via calling pm_runtime_get/put, respectively.

2. To ensure that clocks are managed consistently when generic PM domains are used and are not used, drivers should be migrated to use the tegra_powergate_power_on_legacy() and tegra_powergate_power_off_legacy() functions instead of the current tegra_powergate_sequence_power_up() and tegra_powergate_power_off(). The purpose of the tegra_powergate_power_on_legacy() and tegra_powergate_power_off_legacy() APIs is to mimick the behaviour of the tegra generic power-domain code, such that if generic power domains are not supported the functionality is the same.

Quoted 7 lines

3. The main difference between the tegra_powergate_sequence_power_up() API and the tegra_powergate_power_on_legacy() is that the clock used to enable the powergate is not kept enabled when using the tegra_powergate_power_on_legacy() API. Therefore, drivers must enable the clocks they need after calling tegra_powergate_power_on_legacy() and disable these clocks before calling tegra_powergate_power_off_legacy().

This is the same comment as in the DRM patch, and it applies to all devices that use powergating, so it should move into the preparatory patch rather than be repeated in all patches.

Quoted 87 lines

The helper functions for handling the powering on and off of the PCI controller have been updated to support generic PM domains. The tegra_pcie_power_off() was missing code to disable the clocks enabled in the tegra_pcie_power_on() function and so this has been added.

---
 drivers/pci/host/pci-tegra.c | 49 +++++++++++++++++++++++++++++++++-----------
 1 file changed, 37 insertions(+), 12 deletions(-)
drivers/pci/host/pci-tegra.c +27 -12
index 10c05718dbfd..acd1f311eee5 100644
@@ -40,6 +40,7 @@
 #include <linux/pci.h>
 #include <linux/phy/phy.h>
 #include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
 #include <linux/reset.h>
 #include <linux/sizes.h>
 #include <linux/slab.h>
@@ -908,19 +909,32 @@ static int tegra_pcie_enable_controller(struct tegra_pcie *pcie)
 
 static void tegra_pcie_power_off(struct tegra_pcie *pcie)
 {
+	const struct tegra_pcie_soc_data *soc = pcie->soc_data;
 	int err;
 
-	/* TODO: disable and unprepare clocks? */
-
 	err = phy_power_off(pcie->phy);
 	if (err < 0)
 		dev_warn(pcie->dev, "failed to power off PHY: %d\n", err);
 
 	reset_control_assert(pcie->pcie_xrst);
 	reset_control_assert(pcie->afi_rst);
-	reset_control_assert(pcie->pex_rst);
 
-	tegra_powergate_power_off(TEGRA_POWERGATE_PCIE);
+	clk_disable_unprepare(pcie->pll_e);
+	if (soc->has_cml_clk)
+		clk_disable_unprepare(pcie->cml_clk);
+	clk_disable_unprepare(pcie->afi_clk);
+	clk_disable_unprepare(pcie->pex_clk);
+
+	if (pm_runtime_enabled(pcie->dev)) {
+		err = pm_runtime_put_sync(pcie->dev);
+	} else {
+		err = tegra_powergate_power_off_legacy(TEGRA_POWERGATE_PCIE,
+						       pcie->pex_clk,
+						       pcie->pex_rst);
+	}
+
+	if (err < 0)
+		dev_warn(pcie->dev, "powergate power-down failed: %d\n", err);
 
 	err = regulator_bulk_disable(pcie->num_supplies, pcie->supplies);
 	if (err < 0)
@@ -934,20 +948,28 @@ static int tegra_pcie_power_on(struct tegra_pcie *pcie)
 
 	reset_control_assert(pcie->pcie_xrst);
 	reset_control_assert(pcie->afi_rst);
-	reset_control_assert(pcie->pex_rst);
-
-	tegra_powergate_power_off(TEGRA_POWERGATE_PCIE);
 
 	/* enable regulators */
 	err = regulator_bulk_enable(pcie->num_supplies, pcie->supplies);
 	if (err < 0)
 		dev_err(pcie->dev, "failed to enable regulators: %d\n", err);
 
-	err = tegra_powergate_sequence_power_up(TEGRA_POWERGATE_PCIE,
-						pcie->pex_clk,
-						pcie->pex_rst);
-	if (err) {
-		dev_err(pcie->dev, "powerup sequence failed: %d\n", err);
+	if (pm_runtime_enabled(pcie->dev)) {
+		err = pm_runtime_get_sync(pcie->dev);
+	} else {
+		err = tegra_powergate_power_on_legacy(TEGRA_POWERGATE_PCIE,
+						      pcie->pex_clk,
+						      pcie->pex_rst);
+	}
+
+	if (err < 0) {

drivers/pci/host/pci-tegra.c static int tegra_pcie_power_on(struct tegra_pcie *pcie) · line 975

Quoted 10 lines
drivers/pci/host/pci-tegra.c +7 -0
+		dev_err(pcie->dev, "powergate power-up failed: %d\n", err);
+		return err;
+	}
+
+	err = clk_prepare_enable(pcie->pex_clk);
+	if (err < 0) {
+		dev_err(pcie->dev, "failed to enable PEX clock: %d\n", err);
 		return err;
 	}
 

Couldn't we simply move the above code into suspend/resume functions and then call pm_runtime_get() and pm_runtime_put() instead of tegra_pcie_power_on() and tegra_pcie_power_off()?

drivers/pci/host/pci-tegra.c static int tegra_pcie_probe(struct platform_device *pdev) · line 2027

Quoted 6 lines
drivers/pci/host/pci-tegra.c +2 -0
@@ -2001,6 +2023,9 @@ static int tegra_pcie_probe(struct platform_device *pdev)
 
 	pcibios_min_mem = 0;
 
+	if (of_property_read_bool(pdev->dev.of_node, "power-domains"))
+		pm_runtime_enable(&pdev->dev);

Then of course we'd have to call pm_runtime_enable() irrespective of the property and check for the presence of power domain support via another method (like the dev->pm_domain field).

Thierry
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 819 bytes
Desc: not available
URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20150717/baca9294/attachment.sig>

author maintainer
#38 lore raw

PATCH v3 11/19 PCI: tegra: Add support for generic PM domains

Quoted 129 lines · Thierry Reding

* PGP Signed by an unknown key

Quoted 21 lines · Jon Hunter

Add support to the tegra PCI driver for generic PM domains. However, to ensure backward compatibility with older device tree blobs ensure that the driver can work with or without generic PM domains. In order to migrate to generic PM domain infrastructure the necessary changes are:

1. If the "power-domains" property is present in the DT device node then generic PM domains is supported and pm_runtime_enable() should be called for the device. Furthermore, the enabling and disabling of the power-domain is handled via calling pm_runtime_get/put, respectively.

2. To ensure that clocks are managed consistently when generic PM domains are used and are not used, drivers should be migrated to use the tegra_powergate_power_on_legacy() and tegra_powergate_power_off_legacy() functions instead of the current tegra_powergate_sequence_power_up() and tegra_powergate_power_off(). The purpose of the tegra_powergate_power_on_legacy() and tegra_powergate_power_off_legacy() APIs is to mimick the behaviour of the tegra generic power-domain code, such that if generic power domains are not supported the functionality is the same.

Quoted 7 lines

3. The main difference between the tegra_powergate_sequence_power_up() API and the tegra_powergate_power_on_legacy() is that the clock used to enable the powergate is not kept enabled when using the tegra_powergate_power_on_legacy() API. Therefore, drivers must enable the clocks they need after calling tegra_powergate_power_on_legacy() and disable these clocks before calling tegra_powergate_power_off_legacy().

This is the same comment as in the DRM patch, and it applies to all devices that use powergating, so it should move into the preparatory patch rather than be repeated in all patches.

Quoted 93 lines

The helper functions for handling the powering on and off of the PCI controller have been updated to support generic PM domains. The tegra_pcie_power_off() was missing code to disable the clocks enabled in the tegra_pcie_power_on() function and so this has been added.

---
 drivers/pci/host/pci-tegra.c | 49 +++++++++++++++++++++++++++++++++-----------
 1 file changed, 37 insertions(+), 12 deletions(-)
drivers/pci/host/pci-tegra.c +33 -12
index 10c05718dbfd..acd1f311eee5 100644
@@ -40,6 +40,7 @@
 #include <linux/pci.h>
 #include <linux/phy/phy.h>
 #include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
 #include <linux/reset.h>
 #include <linux/sizes.h>
 #include <linux/slab.h>
@@ -908,19 +909,32 @@ static int tegra_pcie_enable_controller(struct tegra_pcie *pcie)
 
 static void tegra_pcie_power_off(struct tegra_pcie *pcie)
 {
+	const struct tegra_pcie_soc_data *soc = pcie->soc_data;
 	int err;
 
-	/* TODO: disable and unprepare clocks? */
-
 	err = phy_power_off(pcie->phy);
 	if (err < 0)
 		dev_warn(pcie->dev, "failed to power off PHY: %d\n", err);
 
 	reset_control_assert(pcie->pcie_xrst);
 	reset_control_assert(pcie->afi_rst);
-	reset_control_assert(pcie->pex_rst);
 
-	tegra_powergate_power_off(TEGRA_POWERGATE_PCIE);
+	clk_disable_unprepare(pcie->pll_e);
+	if (soc->has_cml_clk)
+		clk_disable_unprepare(pcie->cml_clk);
+	clk_disable_unprepare(pcie->afi_clk);
+	clk_disable_unprepare(pcie->pex_clk);
+
+	if (pm_runtime_enabled(pcie->dev)) {
+		err = pm_runtime_put_sync(pcie->dev);
+	} else {
+		err = tegra_powergate_power_off_legacy(TEGRA_POWERGATE_PCIE,
+						       pcie->pex_clk,
+						       pcie->pex_rst);
+	}
+
+	if (err < 0)
+		dev_warn(pcie->dev, "powergate power-down failed: %d\n", err);
 
 	err = regulator_bulk_disable(pcie->num_supplies, pcie->supplies);
 	if (err < 0)
@@ -934,20 +948,28 @@ static int tegra_pcie_power_on(struct tegra_pcie *pcie)
 
 	reset_control_assert(pcie->pcie_xrst);
 	reset_control_assert(pcie->afi_rst);
-	reset_control_assert(pcie->pex_rst);
-
-	tegra_powergate_power_off(TEGRA_POWERGATE_PCIE);
 
 	/* enable regulators */
 	err = regulator_bulk_enable(pcie->num_supplies, pcie->supplies);
 	if (err < 0)
 		dev_err(pcie->dev, "failed to enable regulators: %d\n", err);
 
-	err = tegra_powergate_sequence_power_up(TEGRA_POWERGATE_PCIE,
-						pcie->pex_clk,
-						pcie->pex_rst);
-	if (err) {
-		dev_err(pcie->dev, "powerup sequence failed: %d\n", err);
+	if (pm_runtime_enabled(pcie->dev)) {
+		err = pm_runtime_get_sync(pcie->dev);
+	} else {
+		err = tegra_powergate_power_on_legacy(TEGRA_POWERGATE_PCIE,
+						      pcie->pex_clk,
+						      pcie->pex_rst);
+	}
+
+	if (err < 0) {
+		dev_err(pcie->dev, "powergate power-up failed: %d\n", err);
+		return err;
+	}
+
+	err = clk_prepare_enable(pcie->pex_clk);
+	if (err < 0) {
Quoted 8 lines

drivers/pci/host/pci-tegra.c static int tegra_pcie_power_on(struct tegra_pcie *pcie) · line 975

Quoted 4 lines
drivers/pci/host/pci-tegra.c +1 -0
+		dev_err(pcie->dev, "failed to enable PEX clock: %d\n", err);
 		return err;
 	}
 

Couldn't we simply move the above code into suspend/resume functions and then call pm_runtime_get() and pm_runtime_put() instead of tegra_pcie_power_on() and tegra_pcie_power_off()?

Possibly, however, the tricky part here is the regulators. We cannot put the regulators into the runtime resume/suspend hooks, because this would mean that the regulators are turned on after and turned off before the power domain. We need to have the regulator on before turning on the power domain.

So there are two options here AFAICT:
1. Move everything above after the regulators are turned on in the runtime resume function.
2. Move handling of all the regulators in the generic power domain code.

What do you think?

Quoted 10 lines

drivers/pci/host/pci-tegra.c static int tegra_pcie_probe(struct platform_device *pdev) · line 2027

Quoted 6 lines
drivers/pci/host/pci-tegra.c +2 -0
@@ -2001,6 +2023,9 @@ static int tegra_pcie_probe(struct platform_device *pdev)
 
 	pcibios_min_mem = 0;
 
+	if (of_property_read_bool(pdev->dev.of_node, "power-domains"))
+		pm_runtime_enable(&pdev->dev);

Then of course we'd have to call pm_runtime_enable() irrespective of the property and check for the presence of power domain support via another method (like the dev->pm_domain field).

Ok.

Jon

author maintainer
#39 lore raw

PATCH v3 12/19 ata: ahci_tegra: Add support for generic PM domains

In reply to Jon Hunter

Add support to the tegra AHCI driver for generic PM domains. However, to ensure backward compatibility with older device tree blobs ensure that the driver can work with or without generic PM domains. In order to migrate to generic PM domain infrastructure the necessary changes are:

1. If the "power-domains" property is present in the DT device node then generic PM domains is supported and the variable has_genpd should be set. The ahci_platform_get_resources()/put_resources() calls the appropriate pm_runtime functions and so no further changes are needed for rpm support.

2. To ensure that clocks are managed consistently when generic PM domains are used and are not used, drivers should be migrated to use the tegra_powergate_power_on_legacy() and tegra_powergate_power_off_legacy() functions instead of the current tegra_powergate_sequence_power_up() and tegra_powergate_power_off(). The purpose of the tegra_powergate_power_on_legacy() and tegra_powergate_power_off_legacy() APIs is to mimick the behaviour of the tegra generic power-domain code, such that if generic power domains are not supported the functionality is the same.

3. The main difference between the tegra_powergate_sequence_power_up() API and the tegra_powergate_power_on_legacy() is that the clock used to enable the powergate is not kept enabled when using the tegra_powergate_power_on_legacy() API. Therefore, drivers must enable the clocks they need after calling tegra_powergate_power_on_legacy() and disable these clocks before calling tegra_powergate_power_off_legacy().

The helper functions for handling the powering on and off of the AHCI controller have been updated to support generic PM domains and the following changes have been made:
a). The clocks for the AHCI controller are managed by
the ahci_platform_enable_resources() and ahci_platform_disable_resources() and so the calls to clock_disable_unprepare() are not needed and can be removed.
b). The tegra->sata_rst is handled by the tegra powergate code and
so does not need to be controlled by the driver.
c). AHCI controller resets should be applied before the clocks are
disabled and so the call to ahci_platform_disable_resources() has been moved to after the reset assertion.

---
 drivers/ata/ahci_tegra.c | 51 ++++++++++++++++++++++++++++++++++--------------
 1 file changed, 36 insertions(+), 15 deletions(-)
drivers/ata/ahci_tegra.c +36 -15
index 3a62eb246d80..438849c15e0e 100644
@@ -23,6 +23,7 @@
 #include <linux/module.h>
 #include <linux/of_device.h>
 #include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
 #include <linux/regulator/consumer.h>
 #include <linux/reset.h>
 
@@ -108,6 +109,7 @@ struct tegra_ahci_priv {
 	/* Needs special handling, cannot use ahci_platform */
 	struct clk		   *sata_clk;
 	struct regulator_bulk_data supplies[5];
+	bool			   has_genpd;
 };
 
 static int tegra_ahci_power_on(struct ahci_host_priv *hpriv)
@@ -120,11 +122,13 @@ static int tegra_ahci_power_on(struct ahci_host_priv *hpriv)
 	if (ret)
 		return ret;
 
-	ret = tegra_powergate_sequence_power_up(TEGRA_POWERGATE_SATA,
-						tegra->sata_clk,
-						tegra->sata_rst);
-	if (ret)
-		goto disable_regulators;
+	if (!tegra->has_genpd) {
+		ret = tegra_powergate_power_on_legacy(TEGRA_POWERGATE_SATA,
+						      tegra->sata_clk,
+						      tegra->sata_rst);
+		if (ret)
+			goto disable_regulators;
+	}
 
 	reset_control_assert(tegra->sata_oob_rst);
 	reset_control_assert(tegra->sata_cold_rst);
@@ -139,9 +143,8 @@ static int tegra_ahci_power_on(struct ahci_host_priv *hpriv)
 	return 0;
 
 disable_power:
-	clk_disable_unprepare(tegra->sata_clk);
-
-	tegra_powergate_power_off(TEGRA_POWERGATE_SATA);
+	tegra_powergate_power_off_legacy(TEGRA_POWERGATE_SATA, tegra->sata_clk,
+					 tegra->sata_rst);
 
 disable_regulators:
 	regulator_bulk_disable(ARRAY_SIZE(tegra->supplies), tegra->supplies);
@@ -149,20 +152,27 @@ disable_regulators:
 	return ret;
 }
 
-static void tegra_ahci_power_off(struct ahci_host_priv *hpriv)
+static int tegra_ahci_power_off(struct ahci_host_priv *hpriv)
 {
 	struct tegra_ahci_priv *tegra = hpriv->plat_data;
+	int ret;
 
-	ahci_platform_disable_resources(hpriv);
-
-	reset_control_assert(tegra->sata_rst);
 	reset_control_assert(tegra->sata_oob_rst);
 	reset_control_assert(tegra->sata_cold_rst);
 
-	clk_disable_unprepare(tegra->sata_clk);
-	tegra_powergate_power_off(TEGRA_POWERGATE_SATA);
+	ahci_platform_disable_resources(hpriv);
+
+	if (!tegra->has_genpd) {
+		ret = tegra_powergate_power_off_legacy(TEGRA_POWERGATE_SATA,
+						       tegra->sata_clk,
+						       tegra->sata_rst);
+		if (ret)
+			return ret;
+	}
 
 	regulator_bulk_disable(ARRAY_SIZE(tegra->supplies), tegra->supplies);
+
+	return 0;
 }
 
 static int tegra_ahci_controller_init(struct ahci_host_priv *hpriv)
@@ -263,7 +273,15 @@ static int tegra_ahci_controller_init(struct ahci_host_priv *hpriv)
 
 static void tegra_ahci_controller_deinit(struct ahci_host_priv *hpriv)
 {
-	tegra_ahci_power_off(hpriv);
+	struct tegra_ahci_priv *tegra = hpriv->plat_data;
+	int ret;
+
+	ret = tegra_ahci_power_off(hpriv);
+
+	if (ret)
+		dev_err(&tegra->pdev->dev,
+			"failed to power off AHCI controller: %d\n", ret);
+
 }
 
 static void tegra_ahci_host_stop(struct ata_host *host)
@@ -356,6 +374,9 @@ static int tegra_ahci_probe(struct platform_device *pdev)
 		return ret;
 	}
 
+	if (of_property_read_bool(pdev->dev.of_node, "power-domains"))
+		tegra->has_genpd = true;
+
 	ret = tegra_ahci_controller_init(hpriv);
 	if (ret)
 		return ret;
Signature · 1 line
2.1.4
author maintainer
#40 lore raw

PATCH v3 13/19 drm/tegra: gr3d: Add support for generic PM domains

In reply to Jon Hunter

Add support to the tegra gr3d driver for generic PM domains. However, to ensure backward compatibility with older device tree blobs ensure that the driver can work with or without generic PM domains. In order to migrate to generic PM domain infrastructure the necessary changes are:

1. If the "power-domains" property is present in the DT device node then generic PM domains is supported and pm_runtime_enable() should be called for the device. Furthermore, the enabling and disabling of the power-domain is handled via calling pm_runtime_get/put, respectively.

2. To ensure that clocks are managed consistently when generic PM domains are used and are not used, drivers should be migrated to use the tegra_powergate_power_on_legacy() and tegra_powergate_power_off_legacy() functions instead of the current tegra_powergate_sequence_power_up() and tegra_powergate_power_off(). The purpose of the tegra_powergate_power_on_legacy() and tegra_powergate_power_off_legacy() APIs is to mimick the behaviour of the tegra generic power-domain code, such that if generic power domains are not supported the functionality is the same.

3. The main difference between the tegra_powergate_sequence_power_up() API and the tegra_powergate_power_on_legacy() is that the clock used to enable the powergate is not kept enabled when using the tegra_powergate_power_on_legacy() API. Therefore, drivers must enable the clocks they need after calling tegra_powergate_power_on_legacy() and disable these clocks before calling tegra_powergate_power_off_legacy().

Helper functions have been added to the gr3d driver for handling power on and off the power-domains with and without generic PM domain support.

---
 drivers/gpu/drm/tegra/gr3d.c | 57 ++++++++++++++++++++++++++++++++++++++------
 1 file changed, 50 insertions(+), 7 deletions(-)
drivers/gpu/drm/tegra/gr3d.c +50 -7
index 0b3f2b977ba0..43a9d50e7e44 100644
@@ -11,6 +11,7 @@
 #include <linux/host1x.h>
 #include <linux/module.h>
 #include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
 #include <linux/reset.h>
 
 #include <soc/tegra/pmc.h>
@@ -238,6 +239,32 @@ static const u32 gr3d_addr_regs[] = {
 	GR3D_GLOBAL_SAMP23SURFADDR(15),
 };
 
+static int gr3d_powergate_on(struct platform_device *pdev, int powergate_id,
+			     struct clk *clk, struct reset_control *rst)
+{
+	int err;
+
+	if (pm_runtime_enabled(&pdev->dev))
+		err = pm_runtime_get_sync(&pdev->dev);
+	else
+		err = tegra_powergate_power_on_legacy(powergate_id,
+						      clk, rst);
+	return err;
+}
+
+static int gr3d_powergate_off(struct platform_device *pdev, int powergate_id,
+			      struct clk *clk, struct reset_control *rst)
+{
+	int err;
+
+	if (pm_runtime_enabled(&pdev->dev))
+		err = pm_runtime_put_sync(&pdev->dev);
+	else
+		err = tegra_powergate_power_off_legacy(powergate_id,
+						       clk, rst);
+	return err;
+}
+
 static int gr3d_probe(struct platform_device *pdev)
 {
 	struct device_node *np = pdev->dev.of_node;
@@ -281,22 +308,37 @@ static int gr3d_probe(struct platform_device *pdev)
 		}
 	}
 
-	err = tegra_powergate_sequence_power_up(TEGRA_POWERGATE_3D, gr3d->clk,
-						gr3d->rst);
+	if (of_property_read_bool(pdev->dev.of_node, "power-domains"))
+		pm_runtime_enable(&pdev->dev);
+
+	err = gr3d_powergate_on(pdev, TEGRA_POWERGATE_3D, gr3d->clk, gr3d->rst);
 	if (err < 0) {
 		dev_err(&pdev->dev, "failed to power up 3D unit\n");
 		return err;
 	}
 
+	err = clk_prepare_enable(gr3d->clk);
+	if (err < 0) {
+		dev_err(&pdev->dev, "failed to enable 3D unit clock\n");
+		return err;
+	}
+
 	if (gr3d->clk_secondary) {
-		err = tegra_powergate_sequence_power_up(TEGRA_POWERGATE_3D1,
-							gr3d->clk_secondary,
-							gr3d->rst_secondary);
+		err = gr3d_powergate_on(pdev, TEGRA_POWERGATE_3D1,
+					gr3d->clk_secondary,
+					gr3d->rst_secondary);
 		if (err < 0) {
 			dev_err(&pdev->dev,
 				"failed to power up secondary 3D unit\n");
 			return err;
 		}
+
+		err = clk_prepare_enable(gr3d->clk_secondary);
+		if (err < 0) {
+			dev_err(&pdev->dev,
+				"failed to enable secondary 3D unit clock\n");
+			return err;
+		}
 	}
 
 	INIT_LIST_HEAD(&gr3d->client.base.list);
@@ -338,12 +380,13 @@ static int gr3d_remove(struct platform_device *pdev)
 	}
 
 	if (gr3d->clk_secondary) {
-		tegra_powergate_power_off(TEGRA_POWERGATE_3D1);
 		clk_disable_unprepare(gr3d->clk_secondary);
+		gr3d_powergate_off(pdev, TEGRA_POWERGATE_3D1,
+				   gr3d->clk_secondary, gr3d->rst_secondary);
 	}
 
-	tegra_powergate_power_off(TEGRA_POWERGATE_3D);
 	clk_disable_unprepare(gr3d->clk);
+	gr3d_powergate_off(pdev, TEGRA_POWERGATE_3D, gr3d->clk, gr3d->rst);
 
 	return 0;
 }
Signature · 1 line
2.1.4
author maintainer
#41 lore raw

PATCH v3 14/19 Documentation: DT: bindings: Add power domain info for NVIDIA PMC

In reply to Jon Hunter

Add power-domain binding documentation for the NVIDIA PMC driver in order to support generic power-domains.

---
 .../bindings/arm/tegra/nvidia,tegra20-pmc.txt      | 99 ++++++++++++++++++++++
 1 file changed, 99 insertions(+)
Documentation/devicetree/bindings/arm/tegra/nvidia,tegra20-pmc.txt +99 -0
index 02c27004d4a8..93357a450576 100644
@@ -1,5 +1,7 @@
 NVIDIA Tegra Power Management Controller (PMC)
 
+== Power Management Controller Node ==
+
 The PMC block interacts with an external Power Management Unit. The PMC
 mostly controls the entry and exit of the system from different sleep
 modes. It provides power-gating controllers for SoC and CPU power-islands.
@@ -68,6 +70,10 @@ Optional properties for hardware-triggered thermal reset (inside 'i2c-thermtrip'
                      Defaults to 0. Valid values are described in section 12.5.2
                      "Pinmux Support" of the Tegra4 Technical Reference Manual.
 
+Optional nodes:
+- pm-domains : This node contains a hierarchy of PM domain nodes, which should
+	       match the power-domains on the Tegra SoC.
+
 Example:
 
 / SoC dts including file
@@ -113,3 +119,96 @@ pmc at 7000f400 {
 	};
 	...
 };
+
+
+== PM Domain Nodes ==
+
+Each of the PM domain nodes represents a power-domain on the Tegra SoC
+that can be power-gated by the PMC and should be named appropriately.
+
+Required properties:
+  - clocks:		   Must contain an entry for each clock required by
+			   the PMC for controlling a power-gate. See
+			   ../clocks/clock-bindings.txt for details.
+  - resets:		   Must contain an entry for each reset required by
+			   the PMC for controlling a power-gate. See
+			   ../reset/reset.txt for details.
+  - nvidia,powergate:	   Integer cell that contains an identifier for the
+			   PMC power-gate that is associated with the
+			   power-domain. Please refer to the Tegra TRM for
+			   more details.
+  - #power-domain-cells:   Must be 0.
+
+Optional properties:
+  - nvidia,swgroups:	   Provides details of the software groups that are
+			   associated with a specific power-domain. The
+			   software group specifier consists of a phandle
+			   pointing to the memory controller and a list of
+			   one or more integer cells for each software group
+			   associated with the power domain. The length of
+			   the list of integer cells is specified by
+			   #nvidia,swgroup-cells.
+  - #nvidia,swgroup-cells: Must be 1 or more. See nvidia,swgroups for
+			   more details.
+
+Example:
+
+	pmc at 0,7000e400 {
+		compatible = "nvidia,tegra124-pmc";
+		reg = <0x0 0x7000e400 0x0 0x400>;
+		clocks = <&tegra_car TEGRA124_CLK_PCLK>, <&clk32k_in>;
+		clock-names = "pclk", "clk32k_in";
+
+		pm-domains {
+
+			...
+
+			pd_sor: sor-power-domain {
+				clocks = <&tegra_car TEGRA124_CLK_SOR0>,
+					 <&tegra_car TEGRA124_CLK_DSIA>,
+					 <&tegra_car TEGRA124_CLK_DSIB>,
+					 <&tegra_car TEGRA124_CLK_HDMI>,
+					 <&tegra_car TEGRA124_CLK_MIPI_CAL>,
+					 <&tegra_car TEGRA124_CLK_DPAUX>;
+				resets = <&tegra_car 182>,
+					 <&tegra_car 48>,
+					 <&tegra_car 82>,
+					 <&tegra_car 51>,
+					 <&tegra_car 56>;
+				nvidia,powergate = <TEGRA_POWERGATE_SOR>;
+				#power-domain-cells = <0>;
+
+				pd_dc: dc-power-domain {
+					clocks = <&tegra_car TEGRA124_CLK_DISP1>;
+					resets = <&tegra_car 27>;
+					nvidia,powergate = <TEGRA_POWERGATE_DIS>;
+					nvidia,swgroups = <&mc TEGRA_SWGROUP_DC>;
+					#nvidia,swgroup-cells = <1>;
+					#power-domain-cells = <0>;
+
+					pd_dcb: dcb-power-domain {
+						clocks = <&tegra_car TEGRA124_CLK_DISP2>;
+						resets = <&tegra_car 26>;
+						nvidia,powergate = <TEGRA_POWERGATE_DISB>;
+						nvidia,swgroups = <&mc TEGRA_SWGROUP_DCB>;
+						#nvidia,swgroup-cells = <1>;
+						#power-domain-cells = <0>;
+					};
+				};
+			};
+		};
+	};
+
+
+== PM Domain Consumers ==
+
+Hardware blocks belonging to a PM domain should contain a "power-domains"
+property that is a phandle pointing to the corresponding PM domain node.
+
+Example:
+
+	sor at 0,54540000 {
+		...
+		power-domains = <&pd_sor>;
+		...
+	};
Signature · 1 line
2.1.4
#42 lore raw

PATCH v3 14/19 Documentation: DT: bindings: Add power domain info for NVIDIA PMC

Quoted 21 lines · Jon Hunter

Add power-domain binding documentation for the NVIDIA PMC driver in order to support generic power-domains.

---
 .../bindings/arm/tegra/nvidia,tegra20-pmc.txt      | 99 ++++++++++++++++++++++
 1 file changed, 99 insertions(+)
Documentation/devicetree/bindings/arm/tegra/nvidia,tegra20-pmc.txt +2 -0
index 02c27004d4a8..93357a450576 100644
@@ -1,5 +1,7 @@
 NVIDIA Tegra Power Management Controller (PMC)
 
+== Power Management Controller Node ==
+
 The PMC block interacts with an external Power Management Unit. The PMC
 mostly controls the entry and exit of the system from different sleep
 modes. It provides power-gating controllers for SoC and CPU power-islands.

Documentation/devicetree/bindings/arm/tegra/nvidia,tegra20-pmc.txt Optional properties for hardware-triggered thermal reset (inside 'i2c-thermtrip' · line 75

Quoted 6 lines
Documentation/devicetree/bindings/arm/tegra/nvidia,tegra20-pmc.txt +3 -0
@@ -68,6 +70,10 @@ Optional properties for hardware-triggered thermal reset (inside 'i2c-thermtrip'
                      Defaults to 0. Valid values are described in section 12.5.2
                      "Pinmux Support" of the Tegra4 Technical Reference Manual.
 
+Optional nodes:
+- pm-domains : This node contains a hierarchy of PM domain nodes, which should
+	       match the power-domains on the Tegra SoC.

Perhaps call this simply power-domains to match the property name in consumer nodes?

Quoted 16 lines
Documentation/devicetree/bindings/arm/tegra/nvidia,tegra20-pmc.txt +9 -0
+
 Example:
 
 / SoC dts including file
@@ -113,3 +119,96 @@ pmc at 7000f400 {
 	};
...
 };
+
+
+== PM Domain Nodes ==
+
+Each of the PM domain nodes represents a power-domain on the Tegra SoC
+that can be power-gated by the PMC and should be named appropriately.
+
+Required properties:

Documentation/devicetree/bindings/arm/tegra/nvidia,tegra20-pmc.txt pmc at 7000f400 { · line 139

Quoted 10 lines
Documentation/devicetree/bindings/arm/tegra/nvidia,tegra20-pmc.txt +10 -0
+  - clocks:		   Must contain an entry for each clock required by
+			   the PMC for controlling a power-gate. See
+			   ../clocks/clock-bindings.txt for details.
+  - resets:		   Must contain an entry for each reset required by
+			   the PMC for controlling a power-gate. See
+			   ../reset/reset.txt for details.
+  - nvidia,powergate:	   Integer cell that contains an identifier for the
+			   PMC power-gate that is associated with the
+			   power-domain. Please refer to the Tegra TRM for
+			   more details.

I find this really difficult to read. Can we use a more conventional format such as:

- clocks: Must contain...
... for details.

?

Documentation/devicetree/bindings/arm/tegra/nvidia,tegra20-pmc.txt pmc at 7000f400 { · line 152

Quoted 13 lines
Documentation/devicetree/bindings/arm/tegra/nvidia,tegra20-pmc.txt +13 -0
+  - #power-domain-cells:   Must be 0.
+
+Optional properties:
+  - nvidia,swgroups:	   Provides details of the software groups that are
+			   associated with a specific power-domain. The
+			   software group specifier consists of a phandle
+			   pointing to the memory controller and a list of
+			   one or more integer cells for each software group
+			   associated with the power domain. The length of
+			   the list of integer cells is specified by
+			   #nvidia,swgroup-cells.
+  - #nvidia,swgroup-cells: Must be 1 or more. See nvidia,swgroups for
+			   more details.

That's not how this usually works. #*-cells properties determine the number of cells required per <phandle spec> pair. So I think this should be more along the lines of:

- #nvidia,swgroups: A list of software groups associated with a specific power domain. This consists of a list of phandle and SWGROUP ID pairs, where the phandle points to the memory controller node.
- #nvidia,swgroup-cells: Must be 1. The single cell in the specifier denotes the ID of the software group.

Documentation/devicetree/bindings/arm/tegra/nvidia,tegra20-pmc.txt pmc at 7000f400 { · line 162

Quoted 10 lines
Documentation/devicetree/bindings/arm/tegra/nvidia,tegra20-pmc.txt +10 -0
+
+Example:
+
+	pmc at 0,7000e400 {
+		compatible = "nvidia,tegra124-pmc";
+		reg = <0x0 0x7000e400 0x0 0x400>;
+		clocks = <&tegra_car TEGRA124_CLK_PCLK>, <&clk32k_in>;
+		clock-names = "pclk", "clk32k_in";
+
+		pm-domains {

power-domains would convey more clearly what this is about.

Documentation/devicetree/bindings/arm/tegra/nvidia,tegra20-pmc.txt pmc at 7000f400 { · line 166

Quoted 3 lines
Documentation/devicetree/bindings/arm/tegra/nvidia,tegra20-pmc.txt +3 -0
+			...
+
+			pd_sor: sor-power-domain {

I don't think it's useful to repeat the -power-domain suffix for all these nodes.

Thierry
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 819 bytes
Desc: not available
URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20150717/9a76a930/attachment.sig>

author maintainer
#43 lore raw

PATCH v3 15/19 soc: tegra: pmc: Add generic PM domain support

In reply to Jon Hunter

Adds generic PM support to the PMC driver where the PM domains are populated from device-tree and the PM domain consumer devices are bound to their relevant PM domains via device-tree as well.

Update the tegra_powergate_power_on_legacy/off_legacy() APIs so that internally they call the same tegra_powergate_xxx functions that are used by the tegra generic power domain code for consistency.

This is based upon work by Thierry Reding <[email protected]> and Vince Hsu <[email protected]>.

---
 drivers/soc/tegra/pmc.c                     | 545 +++++++++++++++++++++++++++-
 include/dt-bindings/power/tegra-powergate.h |  42 +++
 include/soc/tegra/pmc.h                     |  52 +--
 3 files changed, 580 insertions(+), 59 deletions(-)
 create mode 100644 include/dt-bindings/power/tegra-powergate.h
drivers/soc/tegra/pmc.c +533 -12
index 934653785bb7..4de92a9dae65 100644
@@ -19,8 +19,11 @@
 
 #define pr_fmt(fmt) "tegra-pmc: " fmt
 
+#include <dt-bindings/power/tegra-powergate.h>
+
 #include <linux/kernel.h>
 #include <linux/clk.h>
+#include <linux/clk-provider.h>
 #include <linux/clk/tegra.h>
 #include <linux/debugfs.h>
 #include <linux/delay.h>
@@ -30,17 +33,24 @@
 #include <linux/io.h>
 #include <linux/iopoll.h>
 #include <linux/of.h>
+#include <linux/of_platform.h>
 #include <linux/of_address.h>
 #include <linux/platform_device.h>
+#include <linux/pm_domain.h>
 #include <linux/reboot.h>
+#include <linux/regulator/consumer.h>
 #include <linux/reset.h>
+#include <linux/sched.h>
 #include <linux/seq_file.h>
 #include <linux/spinlock.h>
 
 #include <soc/tegra/common.h>
 #include <soc/tegra/fuse.h>
+#include <soc/tegra/mc.h>
 #include <soc/tegra/pmc.h>
 
+#define PMC_POWERGATE_ARRAY_MAX		10
+
 #define PMC_CNTRL			0x0
 #define  PMC_CNTRL_SYSCLK_POLARITY	(1 << 10)  /* sys clk polarity */
 #define  PMC_CNTRL_SYSCLK_OE		(1 << 11)  /* system clock enable */
@@ -106,6 +116,22 @@
 
 #define PMC_PWRGATE_STATE(val, id)	(!!(val & BIT(id)))
 
+struct tegra_powergate {
+	struct generic_pm_domain genpd;
+	struct tegra_pmc *pmc;
+	struct tegra_mc *mc;
+	unsigned int id;
+	struct list_head node;
+	struct device_node *of_node;
+	struct regulator *vdd;
+	struct clk **clks;
+	struct reset_control **resets;
+	const struct tegra_mc_flush **flushes;
+	u32 num_clks;
+	u32 num_resets;
+	u32 num_flushes;
+};
+
 struct tegra_pmc_soc {
 	unsigned int num_powergates;
 	const char *const *powergates;
@@ -118,8 +144,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)
@@ -133,6 +161,7 @@ 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: list of power gates
  * @powergates_lock: mutex for power gate register access
  */
 struct tegra_pmc {
@@ -157,6 +186,7 @@ struct tegra_pmc {
 	u32 lp0_vec_phys;
 	u32 lp0_vec_size;
 
+	struct list_head powergates_list;
 	struct mutex powergates_lock;
 };
 
@@ -165,6 +195,12 @@ static struct tegra_pmc *pmc = &(struct tegra_pmc) {
 	.suspend_mode = TEGRA_SUSPEND_NONE,
 };
 
+static inline struct tegra_powergate *
+to_powergate(struct generic_pm_domain *domain)
+{
+	return container_of(domain, struct tegra_powergate, genpd);
+}
+
 static u32 tegra_pmc_readl(unsigned long offset)
 {
 	return readl(pmc->base + offset);
@@ -175,6 +211,37 @@ static void tegra_pmc_writel(u32 value, unsigned long offset)
 	writel(value, pmc->base + offset);
 }
 
+static int tegra_powergate_get_regulator(struct tegra_powergate *powergate)
+{
+	struct platform_device *pdev;
+
+	if (powergate->id != TEGRA_POWERGATE_EXT)
+		return -EINVAL;
+
+	pdev = of_find_device_by_node(powergate->of_node);
+	if (!pdev)
+		return -EINVAL;
+
+	powergate->vdd = devm_regulator_get_optional(&pdev->dev, "vdd");
+	if (IS_ERR(powergate->vdd))
+		return PTR_ERR(powergate->vdd);
+
+	return 0;
+}
+
+static int tegra_powergate_enable_regulator(struct tegra_powergate *powergate)
+{
+	int ret = 0;
+
+	if (IS_ERR_OR_NULL(powergate->vdd))
+		ret = tegra_powergate_get_regulator(powergate);
+
+	if (!ret)
+		ret = regulator_enable(powergate->vdd);
+
+	return ret;
+}
+
 /**
  * tegra_powergate_set() - set the state of a partition
  * @id: partition ID
@@ -207,6 +274,215 @@ static int tegra_powergate_set(int id, bool new_state, bool wait)
 	return ret;
 }
 
+static int tegra_powergate_enable(struct tegra_powergate *powergate,
+				  bool enable)
+{
+	if (powergate->id != TEGRA_POWERGATE_EXT)
+		return tegra_powergate_set(powergate->id, enable, true);
+
+	if (enable)
+		return tegra_powergate_enable_regulator(powergate);
+	else
+		return regulator_disable(powergate->vdd);
+}
+
+static bool tegra_powergate_is_supported(int id)
+{
+	switch (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:
+		return false;
+	default:
+		return true;
+	}
+}
+
+static bool _tegra_powergate_is_powered(struct tegra_powergate *powergate)
+{
+	if (powergate->id != TEGRA_POWERGATE_EXT)
+		return tegra_powergate_is_powered(powergate->id);
+
+	if (IS_ERR(powergate->vdd))
+		return false;
+	else
+		return regulator_is_enabled(powergate->vdd);
+}
+
+static void tegra_powergate_disable_clocks(struct tegra_powergate *powergate)
+{
+	int i;
+
+	for (i = 0; i < powergate->num_clks; i++)
+		clk_disable_unprepare(powergate->clks[i]);
+}
+
+static int tegra_powergate_enable_clocks(struct tegra_powergate *powergate)
+{
+	int err, i;
+
+	for (i = 0; i < powergate->num_clks; i++) {
+		err = clk_prepare_enable(powergate->clks[i]);
+		if (err)
+			goto out;
+	}
+
+	return 0;
+
+out:
+	while (i--)
+		clk_disable_unprepare(powergate->clks[i]);
+
+	return err;
+}
+
+static int tegra_powergate_mc_flush(struct tegra_powergate *powergate,
+				    bool enable)
+{
+	int i, err;
+
+	for (i = 0; i < powergate->num_flushes; i++) {
+		err = tegra_mc_flush(powergate->mc, powergate->flushes[i],
+				     enable);
+		if (err)
+			return err;
+	}
+
+	return 0;
+}
+
+static int tegra_powergate_reset_assert(struct tegra_powergate *powergate)
+{
+	int err, i;
+
+	for (i = 0; i < powergate->num_resets; i++) {
+		err = reset_control_assert(powergate->resets[i]);
+		if (err)
+			return err;
+	}
+
+	return 0;
+}
+
+static int tegra_powergate_reset_deassert(struct tegra_powergate *powergate)
+{
+	int err, i;
+
+	for (i = 0; i < powergate->num_resets; i++) {
+		err = reset_control_deassert(powergate->resets[i]);
+		if (err)
+			return err;
+	}
+
+	return 0;
+}
+
+static int tegra_powergate_seq_power_up(struct tegra_powergate *powergate)
+{
+	int err;
+
+	err = tegra_powergate_reset_assert(powergate);
+	if (err)
+		goto out;
+	udelay(10);
+
+	err = tegra_powergate_enable(powergate, true);
+	if (err < 0)
+		goto out;
+	udelay(10);
+
+	err = tegra_powergate_enable_clocks(powergate);
+	if (err)
+		goto out;
+	udelay(10);
+
+	tegra_powergate_remove_clamping(powergate->id);
+
+	udelay(10);
+
+	err = tegra_powergate_reset_deassert(powergate);
+	if (err)
+		goto out;
+	udelay(10);
+
+	err = tegra_powergate_mc_flush(powergate, false);
+	if (err)
+		goto out;
+	udelay(10);
+
+	tegra_powergate_disable_clocks(powergate);
+
+	return 0;
+
+out:
+	return err;
+}
+
+static int tegra_powergate_seq_power_down(struct tegra_powergate *powergate)
+{
+	int err;
+
+	err = tegra_powergate_enable_clocks(powergate);
+	if (err)
+		goto out;
+	udelay(10);
+
+	err = tegra_powergate_mc_flush(powergate, true);
+	if (err)
+		goto out;
+	udelay(10);
+
+	err = tegra_powergate_reset_assert(powergate);
+	if (err)
+		goto out;
+	udelay(10);
+
+	tegra_powergate_disable_clocks(powergate);
+
+	udelay(10);
+
+	err = tegra_powergate_enable(powergate, false);
+	if (err)
+		goto out;
+
+	return 0;
+
+out:
+	return err;
+}
+
+static int tegra_genpd_power_on(struct generic_pm_domain *domain)
+{
+	struct tegra_powergate *powergate = to_powergate(domain);
+	struct tegra_pmc *pmc = powergate->pmc;
+	int err;
+
+	err = tegra_powergate_seq_power_up(powergate);
+
+	if (err)
+		dev_err(pmc->dev, "Failed to turn on PM Domain (%d)\n", err);
+
+	return 0;
+}
+
+static int tegra_genpd_power_off(struct generic_pm_domain *domain)
+{
+	struct tegra_powergate *powergate = to_powergate(domain);
+	struct tegra_pmc *pmc = powergate->pmc;
+	int err;
+
+	err = tegra_powergate_seq_power_down(powergate);
+
+	if (err)
+		dev_err(pmc->dev, "Failed to turn off PM Domain (%d)\n", err);
+
+	return err;
+}
+
 /**
  * tegra_powergate_power_on() - power on partition
  * @id: partition ID
@@ -247,6 +523,8 @@ int tegra_powergate_is_powered(int id)
 /**
  * tegra_powergate_remove_clamping() - remove power clamps for partition
  * @id: partition ID
+ *
+ * TODO: make this function static once we get rid of all outside callers
  */
 int tegra_powergate_remove_clamping(int id)
 {
@@ -331,26 +609,41 @@ err_power:
 }
 EXPORT_SYMBOL(tegra_powergate_sequence_power_up);
 
-int tegra_powergate_power_off_legacy(int id, struct clk *clk,
-				     struct reset_control *rst)
+int tegra_powergate_power_on_legacy(int id, struct clk *clk,
+				    struct reset_control *rst)
 {
-	int ret;
+	struct tegra_powergate powergate;
 
-	ret = clk_prepare_enable(clk);
-	if (ret)
-		return ret;
+	if (!pmc->soc || id < 0 || id >= pmc->soc->num_powergates)
+		return -EINVAL;
 
-	usleep_range(10, 20);
+	powergate.id = id;
+	powergate.clks = &clk;
+	powergate.resets = &rst;
+	powergate.num_clks = 1;
+	powergate.num_resets = 1;
+	powergate.num_flushes = 0;
 
-	reset_control_assert(rst);
+	return tegra_powergate_seq_power_up(&powergate);
+}
+EXPORT_SYMBOL(tegra_powergate_power_on_legacy);
 
-	usleep_range(10, 20);
+int tegra_powergate_power_off_legacy(int id, struct clk *clk,
+				     struct reset_control *rst)
+{
+	struct tegra_powergate powergate;
 
-	clk_disable_unprepare(clk);
+	if (!pmc->soc || id < 0 || id >= pmc->soc->num_powergates)
+		return -EINVAL;
 
-	usleep_range(10, 20);
+	powergate.id = id;
+	powergate.clks = &clk;
+	powergate.resets = &rst;
+	powergate.num_clks = 1;
+	powergate.num_resets = 1;
+	powergate.num_flushes = 0;
 
-	return tegra_powergate_power_off(id);
+	return tegra_powergate_seq_power_down(&powergate);
 }
 EXPORT_SYMBOL(tegra_powergate_power_off_legacy);
 
@@ -493,6 +786,228 @@ static int tegra_powergate_debugfs_init(void)
 	return 0;
 }
 
+static int tegra_powergate_of_get_clks(struct device *dev,
+				       struct tegra_powergate *powergate)
+{
+	struct clk *clks[PMC_POWERGATE_ARRAY_MAX];
+	int i = 0;
+
+	for (i = 0; i < PMC_POWERGATE_ARRAY_MAX; i++) {
+		clks[i] = of_clk_get(powergate->of_node, i);
+		if (IS_ERR(clks[i])) {
+			if (PTR_ERR(clks[i]) != -ENOENT)
+				return PTR_ERR(clks[i]);
+			break;
+		}
+	}
+
+	if (PTR_ERR(clks[i]) != -ENOENT) {
+		dev_err(dev, "unsupported number of clocks defined\n");
+		return -EINVAL;
+	}
+
+	powergate->clks = devm_kcalloc(dev, i, sizeof(*clks), GFP_KERNEL);
+	if (!powergate->clks)
+		return -ENOMEM;
+
+	memcpy(powergate->clks, clks, sizeof(*clks) * i);
+	powergate->num_clks = i;
+
+	return 0;
+}
+
+static int tegra_powergate_of_get_resets(struct device *dev,
+					 struct tegra_powergate *powergate)
+{
+	struct reset_control *rsts[PMC_POWERGATE_ARRAY_MAX];
+	int i = 0;
+
+	for (i = 0; i < PMC_POWERGATE_ARRAY_MAX; i++) {
+		rsts[i] = of_reset_control_get_by_index(powergate->of_node, i);
+		if (IS_ERR(rsts[i])) {
+			if (PTR_ERR(rsts[i]) != -ENOENT)
+				return PTR_ERR(rsts[i]);
+			break;
+		}
+	}
+
+	if (PTR_ERR(rsts[i]) != -ENOENT) {
+		dev_err(dev, "unsupported number of resets defined\n");
+		return -EINVAL;
+	}
+
+	powergate->resets = devm_kcalloc(dev, i, sizeof(*rsts), GFP_KERNEL);
+	if (!powergate->resets)
+		return -ENOMEM;
+
+	memcpy(powergate->resets, rsts, sizeof(*rsts) * i);
+	powergate->num_resets = i;
+
+	return 0;
+}
+
+static int tegra_powergate_of_get_mc_flush(struct device *dev,
+					   struct tegra_powergate *powergate)
+{
+	struct platform_device *pdev;
+	struct of_phandle_args args;
+	int i = 0, ret = 0;
+
+	ret = of_parse_phandle_with_args(dev->of_node, "nvidia-swgroups",
+					"#nvidia,swgroup-cells", 0, &args);
+	if (ret < 0) {
+		/*
+		 * The nvidia-swgroups property is
+		 * optional and so if missing simply return.
+		 */
+		if (ret == -ENOENT)
+			return 0;
+
+		dev_err(dev, "nvidia-swgroups property invalid for %s (%d)\n",
+			powergate->of_node->name, ret);
+		return ret;
+	}
+
+	powergate->flushes = devm_kcalloc(dev, args.args_count,
+					  sizeof(powergate->flushes),
+					  GFP_KERNEL);
+	if (!powergate->flushes) {
+		dev_err(dev, "failed to allocate memory for powergate flush\n");
+		return -ENOMEM;
+	}
+
+	pdev = of_find_device_by_node(args.np);
+	if (!pdev)
+		return -ENODEV;
+
+	powergate->mc = platform_get_drvdata(pdev);
+	if (!powergate->mc)
+		return -EINVAL;
+
+	for (i = 0; i < args.args_count; i++) {
+		powergate->flushes[i] = tegra_mc_flush_get(powergate->mc,
+							   args.args[i]);
+		if (!powergate->flushes[i])
+			return -EINVAL;
+	}
+
+	powergate->num_flushes = args.args_count;
+
+	return 0;
+}
+
+static int tegra_powergate_init_powerdomain(struct tegra_pmc *pmc,
+					    struct device_node *np,
+					    struct tegra_powergate *pg)
+{
+	bool off;
+	int err;
+
+	err = of_property_read_u32(np, "nvidia,powergate", &pg->id);
+	if (err) {
+		dev_err(pmc->dev, "no powergate ID for domain\n");
+		goto err;
+	}
+
+	if (tegra_powergate_is_supported(pg->id) == false) {
+		dev_warn(pmc->dev, "%s not currently supported by genpd\n",
+			 np->name);
+		return -EINVAL;
+	}
+
+	if (pg->id == TEGRA_POWERGATE_EXT) {
+		err = tegra_powergate_get_regulator(pg);
+		if (err) {
+			/*
+			 * The regulator might not be ready yet, so just
+			 * give a warning instead of failing the whole
+			 * init.
+			 */
+			dev_warn(pmc->dev, "couldn't locate regulator\n");
+		}
+	}
+
+	pg->of_node = np;
+	pg->genpd.name = np->name;
+	pg->genpd.power_off = tegra_genpd_power_off;
+	pg->genpd.power_on = tegra_genpd_power_on;
+	pg->pmc = pmc;
+
+	err = tegra_powergate_of_get_clks(pmc->dev, pg);
+	if (err)
+		goto err;
+
+	err = tegra_powergate_of_get_resets(pmc->dev, pg);
+	if (err)
+		goto err;
+
+	err = tegra_powergate_of_get_mc_flush(pmc->dev, pg);
+	if (err)
+		goto err;
+
+	list_add_tail(&pg->node, &pmc->powergates_list);
+
+	if (!IS_ERR(pg->vdd) || pg->id != TEGRA_POWERGATE_EXT)
+		tegra_genpd_power_off(&pg->genpd);
+
+	off = !_tegra_powergate_is_powered(pg);
+
+	pm_genpd_init(&pg->genpd, NULL, off);
+
+	dev_info(pmc->dev, "added power domain %s\n", np->name);
+
+	return 0;
+
+err:
+	dev_err(pmc->dev, "failed to add power domain for node %s\n",
+		np->name);
+	return err;
+}
+
+static int tegra_powergate_add_powerdomains(struct tegra_pmc *pmc,
+					    struct device_node *parent,
+					    struct generic_pm_domain *pd_parent)
+{
+	struct device_node *np;
+	int ret;
+
+	for_each_child_of_node(parent, np) {
+		struct tegra_powergate *pg;
+
+		pg = devm_kzalloc(pmc->dev, sizeof(*pg), GFP_KERNEL);
+		if (!pg)
+			return -ENOMEM;
+
+		ret = tegra_powergate_init_powerdomain(pmc, np, pg);
+		if (ret)
+			return ret;
+
+		if (pd_parent)
+			pm_genpd_add_subdomain(pd_parent, &pg->genpd);
+
+		of_genpd_add_provider_simple(np, &pg->genpd);
+
+		tegra_powergate_add_powerdomains(pmc, np, &pg->genpd);
+	}
+
+	return 0;
+}
+
+static int tegra_powergate_init(struct tegra_pmc *pmc)
+{
+	struct device_node *np;
+
+	INIT_LIST_HEAD(&pmc->powergates_list);
+
+	np = of_get_child_by_name(pmc->dev->of_node, "pm-domains");
+	if (!np) {
+		dev_dbg(pmc->dev, "power-domains node not found\n");
+		return 0;
+	}
+
+	return tegra_powergate_add_powerdomains(pmc, np, NULL);
+}
+
 static int tegra_io_rail_prepare(int id, unsigned long *request,
 				 unsigned long *status, unsigned int *bit)
 {
@@ -875,6 +1390,12 @@ static int tegra_pmc_probe(struct platform_device *pdev)
 
 	tegra_pmc_init_tsense_reset(pmc);
 
+	if (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)
include/dt-bindings/power/tegra-powergate.h new file +42 -0

42 lines not on this page · raw message

include/soc/tegra/pmc.h +5 -47

71 lines not on this page · raw message

Signature · 1 line
2.1.4
#44 lore raw

PATCH v3 15/19 soc: tegra: pmc: Add generic PM domain support

Quoted 24 lines · Jon Hunter

Adds generic PM support to the PMC driver where the PM domains are populated from device-tree and the PM domain consumer devices are bound to their relevant PM domains via device-tree as well.

Update the tegra_powergate_power_on_legacy/off_legacy() APIs so that internally they call the same tegra_powergate_xxx functions that are used by the tegra generic power domain code for consistency.

This is based upon work by Thierry Reding <[email protected]> and Vince Hsu <[email protected]>.

---
 drivers/soc/tegra/pmc.c                     | 545 +++++++++++++++++++++++++++-
 include/dt-bindings/power/tegra-powergate.h |  42 +++
 include/soc/tegra/pmc.h                     |  52 +--
 3 files changed, 580 insertions(+), 59 deletions(-)
 create mode 100644 include/dt-bindings/power/tegra-powergate.h
drivers/soc/tegra/pmc.c +0 -0
index 934653785bb7..4de92a9dae65 100644

drivers/soc/tegra/pmc.c line 26

Quoted 8 lines
drivers/soc/tegra/pmc.c +3 -0
@@ -19,8 +19,11 @@
 
 #define pr_fmt(fmt) "tegra-pmc: " fmt
 
+#include <dt-bindings/power/tegra-powergate.h>
+
 #include <linux/kernel.h>
 #include <linux/clk.h>
+#include <linux/clk-provider.h>

Why are we now providing clocks?

Quoted 5 lines
drivers/soc/tegra/pmc.c +0 -0
 #include <linux/clk/tegra.h>
 #include <linux/debugfs.h>
 #include <linux/delay.h>
@@ -30,17 +33,24 @@
 #include <linux/io.h>

drivers/soc/tegra/pmc.c line 43

Quoted 10 lines
drivers/soc/tegra/pmc.c +4 -0
 #include <linux/iopoll.h>
 #include <linux/of.h>
+#include <linux/of_platform.h>
 #include <linux/of_address.h>
 #include <linux/platform_device.h>
+#include <linux/pm_domain.h>
 #include <linux/reboot.h>
+#include <linux/regulator/consumer.h>
 #include <linux/reset.h>
+#include <linux/sched.h>

What do we need this for?

drivers/soc/tegra/pmc.c line 52

Quoted 9 lines
drivers/soc/tegra/pmc.c +2 -0
 #include <linux/seq_file.h>
 #include <linux/spinlock.h>
 
 #include <soc/tegra/common.h>
 #include <soc/tegra/fuse.h>
+#include <soc/tegra/mc.h>
 #include <soc/tegra/pmc.h>
 
+#define PMC_POWERGATE_ARRAY_MAX		10

There should be no need for this.

Quoted 12 lines
drivers/soc/tegra/pmc.c +5 -0
+
 #define PMC_CNTRL			0x0
 #define  PMC_CNTRL_SYSCLK_POLARITY	(1 << 10)  /* sys clk polarity */
 #define  PMC_CNTRL_SYSCLK_OE		(1 << 11)  /* system clock enable */
@@ -106,6 +116,22 @@
 
 #define PMC_PWRGATE_STATE(val, id)	(!!(val & BIT(id)))
 
+struct tegra_powergate {
+	struct generic_pm_domain genpd;
+	struct tegra_pmc *pmc;
+	struct tegra_mc *mc;

drivers/soc/tegra/pmc.c line 132

Quoted 10 lines
drivers/soc/tegra/pmc.c +10 -0
+	unsigned int id;
+	struct list_head node;
+	struct device_node *of_node;
+	struct regulator *vdd;
+	struct clk **clks;
+	struct reset_control **resets;
+	const struct tegra_mc_flush **flushes;
+	u32 num_clks;
+	u32 num_resets;
+	u32 num_flushes;

These should be unsigned int, and please group them with the arrays that they define the sizes of.

drivers/soc/tegra/pmc.c struct tegra_pmc_soc { · line 150

Quoted 13 lines
drivers/soc/tegra/pmc.c +4 -0
+};
+
 struct tegra_pmc_soc {
 	unsigned int num_powergates;
 	const char *const *powergates;
@@ -118,8 +144,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

This could be a separate patch because it isn't related to the power domain work.

Quoted 50 lines
drivers/soc/tegra/pmc.c +22 -0
  * @rate: currently configured rate of pclk
  * @suspend_mode: lowest suspend mode available
  * @cpu_good_time: CPU power good time (in microseconds)
@@ -133,6 +161,7 @@ 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: list of power gates
  * @powergates_lock: mutex for power gate register access
  */
 struct tegra_pmc {
@@ -157,6 +186,7 @@ struct tegra_pmc {
 	u32 lp0_vec_phys;
 	u32 lp0_vec_size;
 
+	struct list_head powergates_list;
 	struct mutex powergates_lock;
 };
 
@@ -165,6 +195,12 @@ static struct tegra_pmc *pmc = &(struct tegra_pmc) {
 	.suspend_mode = TEGRA_SUSPEND_NONE,
 };
 
+static inline struct tegra_powergate *
+to_powergate(struct generic_pm_domain *domain)
+{
+	return container_of(domain, struct tegra_powergate, genpd);
+}
+
 static u32 tegra_pmc_readl(unsigned long offset)
 {
 	return readl(pmc->base + offset);
@@ -175,6 +211,37 @@ static void tegra_pmc_writel(u32 value, unsigned long offset)
 	writel(value, pmc->base + offset);
 }
 
+static int tegra_powergate_get_regulator(struct tegra_powergate *powergate)
+{
+	struct platform_device *pdev;
+
+	if (powergate->id != TEGRA_POWERGATE_EXT)
+		return -EINVAL;
+
+	pdev = of_find_device_by_node(powergate->of_node);
+	if (!pdev)
+		return -EINVAL;
+
+	powergate->vdd = devm_regulator_get_optional(&pdev->dev, "vdd");
+	if (IS_ERR(powergate->vdd))
+		return PTR_ERR(powergate->vdd);

drivers/soc/tegra/pmc.c static void tegra_pmc_writel(u32 value, unsigned long offset) · line 237

Quoted 10 lines
drivers/soc/tegra/pmc.c +10 -0
+
+	return 0;
+}
+
+static int tegra_powergate_enable_regulator(struct tegra_powergate *powergate)
+{
+	int ret = 0;
+
+	if (IS_ERR_OR_NULL(powergate->vdd))
+		ret = tegra_powergate_get_regulator(powergate);

Why don't you get the regulator at the time when the powergate is created? That way you still have a chance of deferring probe to get another chance at getting it. If you don't do that, then if ever you happen to want to enable the powergate before the regulator is ready you'll error out and your consumer will have to deal with that.

Quoted 46 lines
drivers/soc/tegra/pmc.c +39 -0
+
+	if (!ret)
+		ret = regulator_enable(powergate->vdd);
+
+	return ret;
+}
+
 /**
  * tegra_powergate_set() - set the state of a partition
  * @id: partition ID
@@ -207,6 +274,215 @@ static int tegra_powergate_set(int id, bool new_state, bool wait)
 	return ret;
 }
 
+static int tegra_powergate_enable(struct tegra_powergate *powergate,
+				  bool enable)
+{
+	if (powergate->id != TEGRA_POWERGATE_EXT)
+		return tegra_powergate_set(powergate->id, enable, true);
+
+	if (enable)
+		return tegra_powergate_enable_regulator(powergate);
+	else
+		return regulator_disable(powergate->vdd);
+}
+
+static bool tegra_powergate_is_supported(int id)
+{
+	switch (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:
+		return false;
+	default:
+		return true;
+	}
+}
+
+static bool _tegra_powergate_is_powered(struct tegra_powergate *powergate)
+{
+	if (powergate->id != TEGRA_POWERGATE_EXT)
+		return tegra_powergate_is_powered(powergate->id);

drivers/soc/tegra/pmc.c static int tegra_powergate_set(int id, bool new_state, bool wait) · line 318

Quoted 10 lines
drivers/soc/tegra/pmc.c +10 -0
+
+	if (IS_ERR(powergate->vdd))
+		return false;
+	else
+		return regulator_is_enabled(powergate->vdd);
+}
+
+static void tegra_powergate_disable_clocks(struct tegra_powergate *powergate)
+{
+	int i;

Should match the type of powergate->num_clks, that is unsigned int.

drivers/soc/tegra/pmc.c static int tegra_powergate_set(int id, bool new_state, bool wait) · line 326

Quoted 8 lines
drivers/soc/tegra/pmc.c +8 -0
+
+	for (i = 0; i < powergate->num_clks; i++)
+		clk_disable_unprepare(powergate->clks[i]);
+}
+
+static int tegra_powergate_enable_clocks(struct tegra_powergate *powergate)
+{
+	int err, i;

Same here.

Quoted 10 lines
drivers/soc/tegra/pmc.c +10 -0
+
+	for (i = 0; i < powergate->num_clks; i++) {
+		err = clk_prepare_enable(powergate->clks[i]);
+		if (err)
+			goto out;
+	}
+
+	return 0;
+
+out:

drivers/soc/tegra/pmc.c static int tegra_powergate_set(int id, bool new_state, bool wait) · line 346

Quoted 10 lines
drivers/soc/tegra/pmc.c +10 -0
+	while (i--)
+		clk_disable_unprepare(powergate->clks[i]);
+
+	return err;
+}
+
+static int tegra_powergate_mc_flush(struct tegra_powergate *powergate,
+				    bool enable)
+{
+	int i, err;

And here.

Quoted 4 lines
drivers/soc/tegra/pmc.c +4 -0
+
+	for (i = 0; i < powergate->num_flushes; i++) {
+		err = tegra_mc_flush(powergate->mc, powergate->flushes[i],
+				     enable);

drivers/soc/tegra/pmc.c static int tegra_powergate_set(int id, bool new_state, bool wait) · line 360

Quoted 10 lines
drivers/soc/tegra/pmc.c +10 -0
+		if (err)
+			return err;
+	}
+
+	return 0;
+}
+
+static int tegra_powergate_reset_assert(struct tegra_powergate *powergate)
+{
+	int err, i;

As well as here.

Quoted 14 lines
drivers/soc/tegra/pmc.c +14 -0
+
+	for (i = 0; i < powergate->num_resets; i++) {
+		err = reset_control_assert(powergate->resets[i]);
+		if (err)
+			return err;
+	}
+
+	return 0;
+}
+
+static int tegra_powergate_reset_deassert(struct tegra_powergate *powergate)
+{
+	int err, i;
+

drivers/soc/tegra/pmc.c static int tegra_powergate_set(int id, bool new_state, bool wait) · line 384

Quoted 10 lines
drivers/soc/tegra/pmc.c +10 -0
+	for (i = 0; i < powergate->num_resets; i++) {
+		err = reset_control_deassert(powergate->resets[i]);
+		if (err)
+			return err;
+	}
+
+	return 0;
+}
+
+static int tegra_powergate_seq_power_up(struct tegra_powergate *powergate)

What does the _seq buy us here? Why not simply call this tegra_powergate_power_up()?

drivers/soc/tegra/pmc.c static int tegra_powergate_set(int id, bool new_state, bool wait) · line 391

Quoted 7 lines
drivers/soc/tegra/pmc.c +7 -0
+{
+	int err;
+
+	err = tegra_powergate_reset_assert(powergate);
+	if (err)
+		goto out;
+	udelay(10);

There should be a space between the above two lines. Also, is there a reason why we can't use usleep_range() here?

Quoted 13 lines
drivers/soc/tegra/pmc.c +13 -0
+
+	err = tegra_powergate_enable(powergate, true);
+	if (err < 0)
+		goto out;
+	udelay(10);
+
+	err = tegra_powergate_enable_clocks(powergate);
+	if (err)
+		goto out;
+	udelay(10);
+
+	tegra_powergate_remove_clamping(powergate->id);
+

drivers/soc/tegra/pmc.c static int tegra_powergate_set(int id, bool new_state, bool wait) · line 414

Quoted 10 lines
drivers/soc/tegra/pmc.c +10 -0
+	udelay(10);
+
+	err = tegra_powergate_reset_deassert(powergate);
+	if (err)
+		goto out;
+	udelay(10);
+
+	err = tegra_powergate_mc_flush(powergate, false);
+	if (err)
+		goto out;

I mentioned this before, but I think it'd be good to split out the flush changes from this patch, so that we can test the series without the flushing to see if it's at all necessary.

drivers/soc/tegra/pmc.c static int tegra_powergate_set(int id, bool new_state, bool wait) · line 466

Quoted 9 lines
drivers/soc/tegra/pmc.c +9 -0
+static int tegra_genpd_power_on(struct generic_pm_domain *domain)
+{
+	struct tegra_powergate *powergate = to_powergate(domain);
+	struct tegra_pmc *pmc = powergate->pmc;
+	int err;
+
+	err = tegra_powergate_seq_power_up(powergate);
+
+	if (err)

There should be no blank line between the above.

drivers/soc/tegra/pmc.c static int tegra_powergate_set(int id, bool new_state, bool wait) · line 467

Quoted 1 line
drivers/soc/tegra/pmc.c +1 -0
+		dev_err(pmc->dev, "Failed to turn on PM Domain (%d)\n", err);

For consistency with other error messages, "Failed" should be "failed". And "Domain" should be "domain". Also typically the error is separated from the message with a colon: "failed ... domain: %d\n".

Quoted 39 lines
drivers/soc/tegra/pmc.c +21 -11
-int tegra_powergate_power_off_legacy(int id, struct clk *clk,
-				     struct reset_control *rst)
+int tegra_powergate_power_on_legacy(int id, struct clk *clk,
+				    struct reset_control *rst)
 {
-	int ret;
+	struct tegra_powergate powergate;
 
-	ret = clk_prepare_enable(clk);
-	if (ret)
-		return ret;
+	if (!pmc->soc || id < 0 || id >= pmc->soc->num_powergates)
+		return -EINVAL;
 
-	usleep_range(10, 20);
+	powergate.id = id;
+	powergate.clks = &clk;
+	powergate.resets = &rst;
+	powergate.num_clks = 1;
+	powergate.num_resets = 1;
+	powergate.num_flushes = 0;
 
-	reset_control_assert(rst);
+	return tegra_powergate_seq_power_up(&powergate);
+}
+EXPORT_SYMBOL(tegra_powergate_power_on_legacy);
 
-	usleep_range(10, 20);
+int tegra_powergate_power_off_legacy(int id, struct clk *clk,
+				     struct reset_control *rst)
+{
+	struct tegra_powergate powergate;
 
-	clk_disable_unprepare(clk);
+	if (!pmc->soc || id < 0 || id >= pmc->soc->num_powergates)
+		return -EINVAL;
 
-	usleep_range(10, 20);
+	powergate.id = id;

drivers/soc/tegra/pmc.c err_power: · line 648

Quoted 10 lines
drivers/soc/tegra/pmc.c +6 -1
+	powergate.clks = &clk;
+	powergate.resets = &rst;
+	powergate.num_clks = 1;
+	powergate.num_resets = 1;
+	powergate.num_flushes = 0;
 
-	return tegra_powergate_power_off(id);
+	return tegra_powergate_seq_power_down(&powergate);
 }
 EXPORT_SYMBOL(tegra_powergate_power_off_legacy);

It looks as if you decided to reorder the two functions here, but that makes the diff very hard to read.

drivers/soc/tegra/pmc.c static int tegra_powergate_debugfs_init(void) · line 793

Quoted 10 lines
drivers/soc/tegra/pmc.c +5 -0
@@ -493,6 +786,228 @@ static int tegra_powergate_debugfs_init(void)
 	return 0;
 }
 
+static int tegra_powergate_of_get_clks(struct device *dev,
+				       struct tegra_powergate *powergate)
+{
+	struct clk *clks[PMC_POWERGATE_ARRAY_MAX];
+	int i = 0;

unsigned int, and no need to initialize here.

drivers/soc/tegra/pmc.c static int tegra_powergate_debugfs_init(void) · line 802

Quoted 9 lines
drivers/soc/tegra/pmc.c +9 -0
+
+	for (i = 0; i < PMC_POWERGATE_ARRAY_MAX; i++) {
+		clks[i] = of_clk_get(powergate->of_node, i);
+		if (IS_ERR(clks[i])) {
+			if (PTR_ERR(clks[i]) != -ENOENT)
+				return PTR_ERR(clks[i]);
+			break;
+		}
+	}

Can we perhaps prepend this with a loop that determines the number of clocks specified in DT? That way we can avoid the statically sized array altogether.

Perhaps to make it a little easier to read, keep a local variable clk and assign to the array only when it's been sanitized:

	for (...) {
		clk = of_clk_get(...);
		if (IS_ERR(clk)) {
			...
		}

		powergate->clks[i] = clk;
	}
Quoted 5 lines
drivers/soc/tegra/pmc.c +5 -0
+
+	if (PTR_ERR(clks[i]) != -ENOENT) {
+		dev_err(dev, "unsupported number of clocks defined\n");
+		return -EINVAL;
+	}

drivers/soc/tegra/pmc.c static int tegra_powergate_debugfs_init(void) · line 817

Quoted 10 lines
drivers/soc/tegra/pmc.c +10 -0
+
+	powergate->clks = devm_kcalloc(dev, i, sizeof(*clks), GFP_KERNEL);
+	if (!powergate->clks)
+		return -ENOMEM;
+
+	memcpy(powergate->clks, clks, sizeof(*clks) * i);
+	powergate->num_clks = i;
+
+	return 0;
+}

Don't you need to put the clocks that you've obtained above to avoid leaking them?

Quoted 20 lines
drivers/soc/tegra/pmc.c +20 -0
+
+static int tegra_powergate_of_get_resets(struct device *dev,
+					 struct tegra_powergate *powergate)
+{
+	struct reset_control *rsts[PMC_POWERGATE_ARRAY_MAX];
+	int i = 0;
+
+	for (i = 0; i < PMC_POWERGATE_ARRAY_MAX; i++) {
+		rsts[i] = of_reset_control_get_by_index(powergate->of_node, i);
+		if (IS_ERR(rsts[i])) {
+			if (PTR_ERR(rsts[i]) != -ENOENT)
+				return PTR_ERR(rsts[i]);
+			break;
+		}
+	}
+
+	if (PTR_ERR(rsts[i]) != -ENOENT) {
+		dev_err(dev, "unsupported number of resets defined\n");
+		return -EINVAL;
+	}

drivers/soc/tegra/pmc.c static int tegra_powergate_debugfs_init(void) · line 847

Quoted 10 lines
drivers/soc/tegra/pmc.c +10 -0
+
+	powergate->resets = devm_kcalloc(dev, i, sizeof(*rsts), GFP_KERNEL);
+	if (!powergate->resets)
+		return -ENOMEM;
+
+	memcpy(powergate->resets, rsts, sizeof(*rsts) * i);
+	powergate->num_resets = i;
+
+	return 0;
+}

Same comments as for clocks.

Quoted 31 lines
drivers/soc/tegra/pmc.c +31 -0
+static int tegra_powergate_of_get_mc_flush(struct device *dev,
+					   struct tegra_powergate *powergate)
+{
+	struct platform_device *pdev;
+	struct of_phandle_args args;
+	int i = 0, ret = 0;
+
+	ret = of_parse_phandle_with_args(dev->of_node, "nvidia-swgroups",
+					"#nvidia,swgroup-cells", 0, &args);
+	if (ret < 0) {
+		/*
+		 * The nvidia-swgroups property is
+		 * optional and so if missing simply return.
+		 */
+		if (ret == -ENOENT)
+			return 0;
+
+		dev_err(dev, "nvidia-swgroups property invalid for %s (%d)\n",
+			powergate->of_node->name, ret);
+		return ret;
+	}
+
+	powergate->flushes = devm_kcalloc(dev, args.args_count,
+					  sizeof(powergate->flushes),
+					  GFP_KERNEL);
+	if (!powergate->flushes) {
+		dev_err(dev, "failed to allocate memory for powergate flush\n");
+		return -ENOMEM;
+	}
+
+	pdev = of_find_device_by_node(args.np);

drivers/soc/tegra/pmc.c static int tegra_powergate_debugfs_init(void) · line 889

Quoted 10 lines
drivers/soc/tegra/pmc.c +10 -0
+	if (!pdev)
+		return -ENODEV;
+
+	powergate->mc = platform_get_drvdata(pdev);
+	if (!powergate->mc)
+		return -EINVAL;
+
+	for (i = 0; i < args.args_count; i++) {
+		powergate->flushes[i] = tegra_mc_flush_get(powergate->mc,
+							   args.args[i]);

Urgs... might be better to make this a struct device *-based API, so that you don't have to reach into the private structures of the MC driver. Well, it'd technically have to be struct device_node *-based because we don't have a struct device * for the power domains. Perhaps something like this:

	const struct tegra_mc_flush *tegra_mc_flush_get(struct device_node *np,
							unsigned int index);

Then the MC driver can access its own internals, rather than the other way around.

drivers/soc/tegra/pmc.c static int tegra_powergate_debugfs_init(void) · line 901

Quoted 12 lines
drivers/soc/tegra/pmc.c +12 -0
+		if (!powergate->flushes[i])
+			return -EINVAL;
+	}
+
+	powergate->num_flushes = args.args_count;
+
+	return 0;
+}
+
+static int tegra_powergate_init_powerdomain(struct tegra_pmc *pmc,
+					    struct device_node *np,
+					    struct tegra_powergate *pg)

This is slightly confusing. Why not something like this:

struct tegra_powergate *tegra_powergate_create(struct tegra_pmc *pmc,
struct device_node *np)

?

Quoted 15 lines
drivers/soc/tegra/pmc.c +15 -0
+{
+	bool off;
+	int err;
+
+	err = of_property_read_u32(np, "nvidia,powergate", &pg->id);
+	if (err) {
+		dev_err(pmc->dev, "no powergate ID for domain\n");
+		goto err;
+	}
+
+	if (tegra_powergate_is_supported(pg->id) == false) {
+		dev_warn(pmc->dev, "%s not currently supported by genpd\n",
+			 np->name);
+		return -EINVAL;
+	}

drivers/soc/tegra/pmc.c static int tegra_powergate_debugfs_init(void) · line 926

Quoted 10 lines
drivers/soc/tegra/pmc.c +10 -0
+
+	if (pg->id == TEGRA_POWERGATE_EXT) {
+		err = tegra_powergate_get_regulator(pg);
+		if (err) {
+			/*
+			 * The regulator might not be ready yet, so just
+			 * give a warning instead of failing the whole
+			 * init.
+			 */
+			dev_warn(pmc->dev, "couldn't locate regulator\n");

Why won't regular probe deferral work?

Quoted 28 lines
drivers/soc/tegra/pmc.c +28 -0
+		}
+	}
+
+	pg->of_node = np;
+	pg->genpd.name = np->name;
+	pg->genpd.power_off = tegra_genpd_power_off;
+	pg->genpd.power_on = tegra_genpd_power_on;
+	pg->pmc = pmc;
+
+	err = tegra_powergate_of_get_clks(pmc->dev, pg);
+	if (err)
+		goto err;
+
+	err = tegra_powergate_of_get_resets(pmc->dev, pg);
+	if (err)
+		goto err;
+
+	err = tegra_powergate_of_get_mc_flush(pmc->dev, pg);
+	if (err)
+		goto err;
+
+	list_add_tail(&pg->node, &pmc->powergates_list);
+
+	if (!IS_ERR(pg->vdd) || pg->id != TEGRA_POWERGATE_EXT)
+		tegra_genpd_power_off(&pg->genpd);
+
+	off = !_tegra_powergate_is_powered(pg);
+

drivers/soc/tegra/pmc.c static int tegra_powergate_debugfs_init(void) · line 964

Quoted 10 lines
drivers/soc/tegra/pmc.c +10 -0
+	pm_genpd_init(&pg->genpd, NULL, off);
+
+	dev_info(pmc->dev, "added power domain %s\n", np->name);
+
+	return 0;
+
+err:
+	dev_err(pmc->dev, "failed to add power domain for node %s\n",
+		np->name);
+	return err;

This doesn't clean up any of the requested resources on failure.

drivers/soc/tegra/pmc.c static int tegra_powergate_debugfs_init(void) · line 972

Quoted 8 lines
drivers/soc/tegra/pmc.c +8 -0
+}
+
+static int tegra_powergate_add_powerdomains(struct tegra_pmc *pmc,
+					    struct device_node *parent,
+					    struct generic_pm_domain *pd_parent)
+{
+	struct device_node *np;
+	int ret;

Please use err consistently as the name for error codes. This patch uses a mixture of both.

Quoted 9 lines
drivers/soc/tegra/pmc.c +9 -0
+
+	for_each_child_of_node(parent, np) {
+		struct tegra_powergate *pg;
+
+		pg = devm_kzalloc(pmc->dev, sizeof(*pg), GFP_KERNEL);
+		if (!pg)
+			return -ENOMEM;
+
+		ret = tegra_powergate_init_powerdomain(pmc, np, pg);

drivers/soc/tegra/pmc.c static int tegra_powergate_debugfs_init(void) · line 991

Quoted 10 lines
drivers/soc/tegra/pmc.c +10 -0
+		if (ret)
+			return ret;
+
+		if (pd_parent)
+			pm_genpd_add_subdomain(pd_parent, &pg->genpd);
+
+		of_genpd_add_provider_simple(np, &pg->genpd);
+
+		tegra_powergate_add_powerdomains(pmc, np, &pg->genpd);
+	}

Nice, I like the recursive nature of this and how it matches up well with the Tegra hardware.

Quoted 70 lines
drivers/soc/tegra/pmc.c +25 -0
+
+	return 0;
+}
+
+static int tegra_powergate_init(struct tegra_pmc *pmc)
+{
+	struct device_node *np;
+
+	INIT_LIST_HEAD(&pmc->powergates_list);
+
+	np = of_get_child_by_name(pmc->dev->of_node, "pm-domains");
+	if (!np) {
+		dev_dbg(pmc->dev, "power-domains node not found\n");
+		return 0;
+	}
+
+	return tegra_powergate_add_powerdomains(pmc, np, NULL);
+}
+
 static int tegra_io_rail_prepare(int id, unsigned long *request,
 				 unsigned long *status, unsigned int *bit)
 {
@@ -875,6 +1390,12 @@ static int tegra_pmc_probe(struct platform_device *pdev)
 
 	tegra_pmc_init_tsense_reset(pmc);
 
+	if (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)
include/dt-bindings/power/tegra-powergate.h new file +29 -0
new file mode 100644
index 000000000000..b1d51f028a99
@@ -0,0 +1,42 @@
+#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
+#define TEGRA_POWERGATE_NVDEC	25

include/dt-bindings/power/tegra-powergate.h line 39

Quoted 10 lines
include/dt-bindings/power/tegra-powergate.h new file +10 -0
+#define TEGRA_POWERGATE_NVJPG	26
+#define TEGRA_POWERGATE_AUD	27
+#define TEGRA_POWERGATE_DFD	28
+#define TEGRA_POWERGATE_VE2	29
+
+/*
+ * Pseudo powergate for power-domains that are power-gated externally.
+ * Make this a large number to allow other powergates to be added.
+ */
+#define TEGRA_POWERGATE_EXT	1000

Can't we simply cover this case by not specifying the nvidia,powergate property in the first place? Or make it conditional on the existence of a vdd-supply property.

include/dt-bindings/power/tegra-powergate.h line 41

Quoted 2 lines
include/dt-bindings/power/tegra-powergate.h new file +2 -0
+
+

There's a gratuitous blank line here.

Thierry
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 819 bytes
Desc: not available
URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20150717/120b7be1/attachment.sig>

author maintainer
#45 lore raw

PATCH v3 16/19 soc: tegra: pmc: Remove the deprecated powergate APIs

In reply to Jon Hunter

Drivers using the deprecated tegra powergate APIs have been updated and so now we can remove these unused deprecated APIs.

---
 drivers/soc/tegra/pmc.c | 68 -------------------------------------------------
 include/soc/tegra/pmc.h | 22 ----------------
 2 files changed, 90 deletions(-)
drivers/soc/tegra/pmc.c +0 -68
index 4de92a9dae65..402f13727e75 100644
@@ -484,31 +484,6 @@ static int tegra_genpd_power_off(struct generic_pm_domain *domain)
 }
 
 /**
- * tegra_powergate_power_on() - power on partition
- * @id: partition ID
- */
-int tegra_powergate_power_on(int id)
-{
-	if (!pmc->soc || id < 0 || id >= pmc->soc->num_powergates)
-		return -EINVAL;
-
-	return tegra_powergate_set(id, true, true);
-}
-
-/**
- * tegra_powergate_power_off() - power off partition
- * @id: partition ID
- */
-int tegra_powergate_power_off(int id)
-{
-	if (!pmc->soc || id < 0 || id >= pmc->soc->num_powergates)
-		return -EINVAL;
-
-	return tegra_powergate_set(id, false, true);
-}
-EXPORT_SYMBOL(tegra_powergate_power_off);
-
-/**
  * tegra_powergate_is_powered() - check if partition is powered
  * @id: partition ID
  */
@@ -566,49 +541,6 @@ int tegra_powergate_remove_clamping(int id)
 }
 EXPORT_SYMBOL(tegra_powergate_remove_clamping);
 
-/**
- * tegra_powergate_sequence_power_up() - power up partition
- * @id: partition ID
- * @clk: clock for partition
- * @rst: reset for partition
- *
- * Must be called with clk disabled, and returns with clk enabled.
- */
-int tegra_powergate_sequence_power_up(int id, struct clk *clk,
-				      struct reset_control *rst)
-{
-	int ret;
-
-	reset_control_assert(rst);
-
-	ret = tegra_powergate_power_on(id);
-	if (ret)
-		goto err_power;
-
-	ret = clk_prepare_enable(clk);
-	if (ret)
-		goto err_clk;
-
-	usleep_range(10, 20);
-
-	ret = tegra_powergate_remove_clamping(id);
-	if (ret)
-		goto err_clamp;
-
-	usleep_range(10, 20);
-	reset_control_deassert(rst);
-
-	return 0;
-
-err_clamp:
-	clk_disable_unprepare(clk);
-err_clk:
-	tegra_powergate_power_off(id);
-err_power:
-	return ret;
-}
-EXPORT_SYMBOL(tegra_powergate_sequence_power_up);
-
 int tegra_powergate_power_on_legacy(int id, struct clk *clk,
 				    struct reset_control *rst)
 {
include/soc/tegra/pmc.h +0 -22
index 67b75d82edc7..509c1a3ca879 100644
@@ -79,14 +79,8 @@ int tegra_pmc_cpu_remove_clamping(int cpuid);
 
 #ifdef CONFIG_ARCH_TEGRA
 int tegra_powergate_is_powered(int id);
-int tegra_powergate_power_on(int id);
-int tegra_powergate_power_off(int id);
 int tegra_powergate_remove_clamping(int id);
 
-/* Must be called with clk disabled, and returns with clk enabled */
-int tegra_powergate_sequence_power_up(int id, struct clk *clk,
-				      struct reset_control *rst);
-
 int tegra_powergate_power_on_legacy(int id, struct clk *clk,
 				    struct reset_control *rst);
 
@@ -101,27 +95,11 @@ static inline int tegra_powergate_is_powered(int id)
 	return -ENOSYS;
 }
 
-static inline int tegra_powergate_power_on(int id)
-{
-	return -ENOSYS;
-}
-
-static inline int tegra_powergate_power_off(int id)
-{
-	return -ENOSYS;
-}
-
 static inline int tegra_powergate_remove_clamping(int id)
 {
 	return -ENOSYS;
 }
 
-static inline int tegra_powergate_sequence_power_up(int id, struct clk *clk,
-						    struct reset_control *rst)
-{
-	return -ENOSYS;
-}
-
 static inline int tegra_io_rail_power_on(int id)
 {
 	return -ENOSYS;
Signature · 1 line
2.1.4
author maintainer
#46 lore raw

PATCH v3 17/19 ARM: tegra: Add PM domain device nodes to Tegra124 DT

In reply to Jon Hunter

Add tegra124 pm-domains provider and consumer nodes.

---
 arch/arm/boot/dts/tegra124.dtsi | 80 +++++++++++++++++++++++++++++++++++++++++
 1 file changed, 80 insertions(+)
arch/arm/boot/dts/tegra124.dtsi +80 -0
index 87318a72f615..3573bb079791 100644
@@ -3,6 +3,7 @@
 #include <dt-bindings/memory/tegra124-mc.h>
 #include <dt-bindings/pinctrl/pinctrl-tegra.h>
 #include <dt-bindings/pinctrl/pinctrl-tegra-xusb.h>
+#include <dt-bindings/power/tegra-powergate.h>
 #include <dt-bindings/interrupt-controller/arm-gic.h>
 #include <dt-bindings/reset/tegra124-car.h>
 #include <dt-bindings/thermal/tegra124-soctherm.h>
@@ -40,6 +41,8 @@
 			  0x82000000 0 0x13000000 0x0 0x13000000 0 0x0d000000   /* non-prefetchable memory (208 MiB) */
 			  0xc2000000 0 0x20000000 0x0 0x20000000 0 0x20000000>; /* prefetchable memory (512 MiB) */
 
+		power-domains = <&pd_pcie>;
+
 		clocks = <&tegra_car TEGRA124_CLK_PCIE>,
 			 <&tegra_car TEGRA124_CLK_AFI>,
 			 <&tegra_car TEGRA124_CLK_PLL_E>,
@@ -99,6 +102,7 @@
 			compatible = "nvidia,tegra124-dc";
 			reg = <0x0 0x54200000 0x0 0x00040000>;
 			interrupts = <GIC_SPI 73 IRQ_TYPE_LEVEL_HIGH>;
+			power-domains = <&pd_dc>;
 			clocks = <&tegra_car TEGRA124_CLK_DISP1>,
 				 <&tegra_car TEGRA124_CLK_PLL_P>;
 			clock-names = "dc", "parent";
@@ -114,6 +118,7 @@
 			compatible = "nvidia,tegra124-dc";
 			reg = <0x0 0x54240000 0x0 0x00040000>;
 			interrupts = <GIC_SPI 74 IRQ_TYPE_LEVEL_HIGH>;
+			power-domains = <&pd_dcb>;
 			clocks = <&tegra_car TEGRA124_CLK_DISP2>,
 				 <&tegra_car TEGRA124_CLK_PLL_P>;
 			clock-names = "dc", "parent";
@@ -141,6 +146,7 @@
 			compatible = "nvidia,tegra124-sor";
 			reg = <0x0 0x54540000 0x0 0x00040000>;
 			interrupts = <GIC_SPI 76 IRQ_TYPE_LEVEL_HIGH>;
+			power-domains = <&pd_sor>;
 			clocks = <&tegra_car TEGRA124_CLK_SOR0>,
 				 <&tegra_car TEGRA124_CLK_PLL_D_OUT0>,
 				 <&tegra_car TEGRA124_CLK_PLL_DP>,
@@ -155,6 +161,7 @@
 			compatible = "nvidia,tegra124-dpaux";
 			reg = <0x0 0x545c0000 0x0 0x00040000>;
 			interrupts = <GIC_SPI 159 IRQ_TYPE_LEVEL_HIGH>;
+			power-domains = <&pd_sor>;
 			clocks = <&tegra_car TEGRA124_CLK_DPAUX>,
 				 <&tegra_car TEGRA124_CLK_PLL_DP>;
 			clock-names = "dpaux", "parent";
@@ -184,6 +191,7 @@
 		interrupts = <GIC_SPI 157 IRQ_TYPE_LEVEL_HIGH>,
 			     <GIC_SPI 158 IRQ_TYPE_LEVEL_HIGH>;
 		interrupt-names = "stall", "nonstall";
+		power-domains = <&pd_gpu>;
 		clocks = <&tegra_car TEGRA124_CLK_GPU>,
 			 <&tegra_car TEGRA124_CLK_PLL_P_OUT5>;
 		clock-names = "gpu", "pwr";
@@ -573,6 +581,76 @@
 		reg = <0x0 0x7000e400 0x0 0x400>;
 		clocks = <&tegra_car TEGRA124_CLK_PCLK>, <&clk32k_in>;
 		clock-names = "pclk", "clk32k_in";
+
+		pm-domains {
+			pd_gpu: gpu-power-domain {
+				clocks = <&tegra_car TEGRA124_CLK_GPU>,
+					 <&tegra_car TEGRA124_CLK_PLL_P_OUT5>;
+				resets = <&tegra_car 184>;
+				nvidia,powergate = <TEGRA_POWERGATE_EXT>;
+				nvidia,swgroups = <&mc TEGRA_SWGROUP_GPU>;
+				#nvidia,swgroup-cells = <1>;
+				#power-domain-cells = <0>;
+			};
+
+			pd_pcie: pcie-power-domain {
+				clocks = <&tegra_car TEGRA124_CLK_PCIE>,
+					 <&tegra_car TEGRA124_CLK_AFI>;
+				resets = <&tegra_car 70>,
+					 <&tegra_car 72>;
+				nvidia,powergate = <TEGRA_POWERGATE_PCIE>;
+				nvidia,swgroups = <&mc TEGRA_SWGROUP_AFI>;
+				#nvidia,swgroup-cells = <1>;
+				#power-domain-cells = <0>;
+			};
+
+			pd_sata: sata-power-domain {
+				clocks = <&tegra_car TEGRA124_CLK_SATA>,
+					 <&tegra_car TEGRA124_CLK_SATA_OOB>,
+					 <&tegra_car TEGRA124_CLK_CML1>;
+				resets = <&tegra_car 124>,
+					 <&tegra_car 123>,
+					 <&tegra_car 129>;
+				nvidia,powergate = <TEGRA_POWERGATE_SATA>;
+				nvidia,swgroups = <&mc TEGRA_SWGROUP_SATA>;
+				#nvidia,swgroup-cells = <1>;
+				#power-domain-cells = <0>;
+			};
+
+			pd_sor: sor-power-domain {
+				clocks = <&tegra_car TEGRA124_CLK_SOR0>,
+					 <&tegra_car TEGRA124_CLK_DSIA>,
+					 <&tegra_car TEGRA124_CLK_DSIB>,
+					 <&tegra_car TEGRA124_CLK_HDMI>,
+					 <&tegra_car TEGRA124_CLK_MIPI_CAL>,
+					 <&tegra_car TEGRA124_CLK_DPAUX>;
+				resets = <&tegra_car 182>,
+					 <&tegra_car 48>,
+					 <&tegra_car 82>,
+					 <&tegra_car 51>,
+					 <&tegra_car 56>;
+				nvidia,powergate = <TEGRA_POWERGATE_SOR>;
+				#power-domain-cells = <0>;
+
+				pd_dc: dc-power-domain {
+					clocks = <&tegra_car TEGRA124_CLK_DISP1>;
+					resets = <&tegra_car 27>;
+					nvidia,powergate = <TEGRA_POWERGATE_DIS>;
+					nvidia,swgroups = <&mc TEGRA_SWGROUP_DC>;
+					#nvidia,swgroup-cells = <1>;
+					#power-domain-cells = <0>;
+
+					pd_dcb: dcb-power-domain {
+						clocks = <&tegra_car TEGRA124_CLK_DISP2>;
+						resets = <&tegra_car 26>;
+						nvidia,powergate = <TEGRA_POWERGATE_DISB>;
+						nvidia,swgroups = <&mc TEGRA_SWGROUP_DCB>;
+						#nvidia,swgroup-cells = <1>;
+						#power-domain-cells = <0>;
+					};
+				};
+			};
+		};
 	};
 
 	fuse at 0,7000f800 {
@@ -621,6 +699,8 @@
 			<&tegra_car 129>;
 		reset-names = "sata", "sata-oob", "sata-cold";
 
+		power-domains = <&pd_sata>;
+
 		phys = <&padctl TEGRA_XUSB_PADCTL_SATA>;
 		phy-names = "sata-phy";
 
Signature · 1 line
2.1.4
author maintainer
#47 lore raw

PATCH v3 18/19 ARM: tegra: add GPU power supply to Jetson TK1 DT

In reply to Jon Hunter

From: Vince Hsu <[email protected]>

Add power supply information which is board dependent for GK20A.

---
 arch/arm/boot/dts/tegra124-jetson-tk1.dts | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)
arch/arm/boot/dts/tegra124-jetson-tk1.dts +5 -1
index ce5961cd0b77..2b355957635c 100644
@@ -1514,7 +1514,7 @@
 					regulator-always-on;
 				};
 
-				sd6 {
+				vdd_gpu: sd6 {
 					regulator-name = "+VDD_GPU_AP";
 					regulator-min-microvolt = <650000>;
 					regulator-max-microvolt = <1200000>;
@@ -1635,6 +1635,10 @@
 		};
 	};
 
+	gpu-power-domain {
+		vdd-supply = <&vdd_gpu>;
+	};
+
 	/* Serial ATA */
 	sata at 0,70020000 {
 		status = "okay";
Signature · 1 line
2.1.4
#48 lore raw

PATCH v3 18/19 ARM: tegra: add GPU power supply to Jetson TK1 DT

Quoted 24 lines · Jon Hunter

From: Vince Hsu <[email protected]>

Add power supply information which is board dependent for GK20A.

---
 arch/arm/boot/dts/tegra124-jetson-tk1.dts | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)
arch/arm/boot/dts/tegra124-jetson-tk1.dts +1 -1
index ce5961cd0b77..2b355957635c 100644
@@ -1514,7 +1514,7 @@
 					regulator-always-on;
 				};
 
-				sd6 {
+				vdd_gpu: sd6 {
 					regulator-name = "+VDD_GPU_AP";
 					regulator-min-microvolt = <650000>;
 					regulator-max-microvolt = <1200000>;

arch/arm/boot/dts/tegra124-jetson-tk1.dts line 1640

Quoted 6 lines
arch/arm/boot/dts/tegra124-jetson-tk1.dts +3 -0
@@ -1635,6 +1635,10 @@
 		};
 	};
 
+	gpu-power-domain {
+		vdd-supply = <&vdd_gpu>;
+	};

This doesn't seem to match the path to the original node.

Thierry
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 819 bytes
Desc: not available
URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20150717/6e42b0a0/attachment.sig>

author maintainer
#49 lore raw

PATCH v3 19/19 ARM: tegra: select PM_GENERIC_DOMAINS

In reply to Jon Hunter

From: Vince Hsu <[email protected]>

We have added generic power domain support for Tegra SoCs. So now the option M_GENERIC_DOMAINS must be enabled by default to have proper power sequence.

---
 arch/arm/mach-tegra/Kconfig | 1 +
 1 file changed, 1 insertion(+)
arch/arm/mach-tegra/Kconfig +1 -0
index 0fa4c5f8b1be..fdf86cae3b32 100644
@@ -9,6 +9,7 @@ menuconfig ARCH_TEGRA
 	select HAVE_ARM_TWD if SMP
 	select PINCTRL
 	select PM_OPP
+	select PM_GENERIC_DOMAINS if PM
 	select ARCH_HAS_RESET_CONTROLLER
 	select RESET_CONTROLLER
 	select SOC_BUS
Signature · 1 line
2.1.4
#50 lore raw

PATCH v3 19/19 ARM: tegra: select PM_GENERIC_DOMAINS

Quoted 4 lines · Jon Hunter

From: Vince Hsu <[email protected]>

We have added generic power domain support for Tegra SoCs. So now the option M_GENERIC_DOMAINS must be enabled by default to have proper power

Missing P :)

Quoted 23 lines

sequence.

---
 arch/arm/mach-tegra/Kconfig | 1 +
 1 file changed, 1 insertion(+)
arch/arm/mach-tegra/Kconfig +1 -0
index 0fa4c5f8b1be..fdf86cae3b32 100644
@@ -9,6 +9,7 @@ menuconfig ARCH_TEGRA
 	select HAVE_ARM_TWD if SMP
 	select PINCTRL
 	select PM_OPP
+	select PM_GENERIC_DOMAINS if PM
 	select ARCH_HAS_RESET_CONTROLLER
 	select RESET_CONTROLLER
 	select SOC_BUS
Signature · 1 line
2.1.4
author maintainer
#51 lore raw

PATCH v3 19/19 ARM: tegra: select PM_GENERIC_DOMAINS

Quoted 7 lines · Peter De Schrijver
Quoted 4 lines · Jon Hunter

From: Vince Hsu <[email protected]>

We have added generic power domain support for Tegra SoCs. So now the option M_GENERIC_DOMAINS must be enabled by default to have proper power

Missing P :)

Ooops. I think introduced that error. Will fix.

Cheers
Jon

author maintainer
#52 lore raw

PATCH v3 19/19 ARM: tegra: select PM_GENERIC_DOMAINS

Quoted 4 lines · Jon Hunter
Quoted 1 line · Peter De Schrijver

On Mon, Jul 13, 2015 at 01:39:57PM +0100, Jon Hunter wrote:

Quoted 8 lines
Quoted 6 lines
Quoted 4 lines

From: Vince Hsu <[email protected]>

We have added generic power domain support for Tegra SoCs. So now the option M_GENERIC_DOMAINS must be enabled by default to have proper power

Missing P :)

Ooops. I think introduced that error. Will fix.

Updated version ...

Quoted 1 line

From f7b951cd05973d74fc25e33a1aaec5a6e77bfdc0 Mon Sep 17 00:00:00 2001

From: Vince Hsu <[email protected]>
Date: Wed, 11 Mar 2015 13:40:46 +0800
Subject: [PATCH 19/19] ARM: tegra: select PM_GENERIC_DOMAINS

We have added generic power domain support for Tegra SoCs. So now the option PM_GENERIC_DOMAINS must be enabled by default to have proper power sequence.

---
 arch/arm/mach-tegra/Kconfig | 1 +
 1 file changed, 1 insertion(+)
arch/arm/mach-tegra/Kconfig +1 -0
index 0fa4c5f8b1be..fdf86cae3b32 100644
@@ -9,6 +9,7 @@ menuconfig ARCH_TEGRA
 	select HAVE_ARM_TWD if SMP
 	select PINCTRL
 	select PM_OPP
+	select PM_GENERIC_DOMAINS if PM
 	select ARCH_HAS_RESET_CONTROLLER
 	select RESET_CONTROLLER
 	select SOC_BUS
Signature · 1 line
2.1.4
#53 lore raw

PATCH v3 19/19 ARM: tegra: select PM_GENERIC_DOMAINS

Quoted 9 lines · Jon Hunter
Quoted 6 lines · Jon Hunter
Quoted 3 lines · Peter De Schrijver
Quoted 2 lines · Jon Hunter

From: Vince Hsu <[email protected]>

Quoted 9 lines
Quoted 6 lines
Quoted 4 lines
Quoted 2 lines

We have added generic power domain support for Tegra SoCs. So now the option M_GENERIC_DOMAINS must be enabled by default to have proper power

Missing P :)

Ooops. I think introduced that error. Will fix.

Updated version ...

Quoted 31 lines

From f7b951cd05973d74fc25e33a1aaec5a6e77bfdc0 Mon Sep 17 00:00:00 2001
From: Vince Hsu <[email protected]>
Date: Wed, 11 Mar 2015 13:40:46 +0800
Subject: [PATCH 19/19] ARM: tegra: select PM_GENERIC_DOMAINS

We have added generic power domain support for Tegra SoCs. So now the option PM_GENERIC_DOMAINS must be enabled by default to have proper power sequence.

---
 arch/arm/mach-tegra/Kconfig | 1 +
 1 file changed, 1 insertion(+)
arch/arm/mach-tegra/Kconfig +1 -0
index 0fa4c5f8b1be..fdf86cae3b32 100644
@@ -9,6 +9,7 @@ menuconfig ARCH_TEGRA
 	select HAVE_ARM_TWD if SMP
 	select PINCTRL
 	select PM_OPP
+	select PM_GENERIC_DOMAINS if PM
 	select ARCH_HAS_RESET_CONTROLLER
 	select RESET_CONTROLLER
 	select SOC_BUS
Signature · 1 line
2.1.4