2021-09-26 22:43:32

by Dmitry Osipenko

[permalink] [raw]
Subject: [PATCH v13 00/35] NVIDIA Tegra power management patches for 5.16

This series adds runtime PM support to Tegra drivers and enables core
voltage scaling for Tegra20/30 SoCs, resolving overheating troubles.

All patches in this series are interdependent and should go via Tegra tree.

Changelog:

v13: - Fixed compile-test error reported by build bot by reverting the
mmc/ patch to v11. The sdhci_suspend/resume_host() functions aren't
available with the disabled CONFIG_PM_SLEEP, some code needs the
ifdef.

- Added last r-b from Rob Herring for the DT patches.

- Corrected clk/ PM domain-support patch by not using the
devm_tegra_core_dev_init_opp_table_common() helper, which I
utilized in v12. The clk driver implements its own power domain
state syncing and common helper shouldn't be used. This fixes driver
probing for some clocks on some devices. It was reported by
Svyatoslav Ryhel for PLLE OPP error on T30 Asus Transformer tablet.

v12: - Added r-b from Rob Herring to the host1x binding patch.

- Added acks from Hans Verkuil to the video decoder patches.

- In the v11 changelog I forgot to mention that the clk-binding
patch was also changed with a corrected regex pattern and removed
'clocks' sub-node. This patch needs r-b or ack too.

- Added new "Rename 3d power domains" patch to match the DT schema
naming requirement. Thanks to David Heidelberg for spotting this
problem.

- Replaced #ifdef CONFIG_PM_SLEEP with maybe_unused in the MMC patch
to make code cleaner.

v11: - Added acks and r-b from Rob Herring, Mark Brown and Miquel Raynal
that were given to v8.

- Corrected order of the new memory controller reset entry in
device-trees and host1x DT binding patch, which was requested by
Rob Herring.

- Switched consumer drivers to use power domain state syncing done
by new Tegra's common OPP-initialization helper.

- Made use of new devm_pm_runtime_enable() helper that was added to
v5.15 kernel, where appropriate.

- Added "fuse: Use resource-managed helpers" patch.

- Converted Tegra20/30 clk drivers to a proper platform drivers,
which was requested by Thierry Reding.

- Removed clk-bulk API usage from the MMC patch, which was requested
by Thierry Reding.

- Changed CORE power domain name to "core" in a new patch
"Change name of core power domain".

- Misc small fixes for problems that I found since v8, like couple
typos in error code paths and restored working RPM for Tegra DRM
UAPI v1 that was removed in v8 by accident.

v9-v10: Figured out remaining GENPD API changes with Ulf Hansson and
Viresh Kumar. The OPP-sync helper that was used in v8 isn't needed
anymore because GENPD API now allows consumer drivers to
init rpm_pstate of power domains.

v8: - Added new generic dev_pm_opp_sync() helper that syncs OPP state with
hardware. All drivers changed to use it. This replaces GENPD attach_dev
callback hacks that were used in v7.

- Added new patch patch "soc/tegra: regulators: Prepare for suspend"
that fixes dying Tegra20 SoC after enabling VENC power domain during
resume from suspend. It matches to what downstream kernel does on
suspend/resume.

- After a second thought, I dropped patches which added RPM to memory
drivers since hardware is always-on and RPM not needed.

- Replaced the "dummy host1x driver" patch with new "Disable unused
host1x hardware" patch, since it's a cleaner solution.

Dmitry Osipenko (35):
opp: Change type of dev_pm_opp_attach_genpd(names) argument
soc/tegra: Add devm_tegra_core_dev_init_opp_table_common()
soc/tegra: pmc: Disable PMC state syncing
soc/tegra: Don't print error message when OPPs not available
dt-bindings: clock: tegra-car: Document new clock sub-nodes
clk: tegra: Support runtime PM and power domain
dt-bindings: host1x: Document OPP and power domain properties
dt-bindings: host1x: Document Memory Client resets of Host1x, GR2D and
GR3D
gpu: host1x: Add runtime PM and OPP support
gpu: host1x: Add host1x_channel_stop()
drm/tegra: dc: Support OPP and SoC core voltage scaling
drm/tegra: hdmi: Add OPP support
drm/tegra: gr2d: Support generic power domain and runtime PM
drm/tegra: gr3d: Support generic power domain and runtime PM
drm/tegra: vic: Support system suspend
usb: chipidea: tegra: Add runtime PM and OPP support
bus: tegra-gmi: Add runtime PM and OPP support
pwm: tegra: Add runtime PM and OPP support
mmc: sdhci-tegra: Add runtime PM and OPP support
mtd: rawnand: tegra: Add runtime PM and OPP support
spi: tegra20-slink: Add OPP support
media: dt: bindings: tegra-vde: Convert to schema
media: dt: bindings: tegra-vde: Document OPP and power domain
media: staging: tegra-vde: Support generic power domain
soc/tegra: fuse: Reset hardware
soc/tegra: fuse: Use resource-managed helpers
soc/tegra: regulators: Prepare for suspend
soc/tegra: pmc: Rename 3d power domains
soc/tegra: pmc: Rename core power domain
soc/tegra: pmc: Enable core domain support for Tegra20 and Tegra30
ARM: tegra: Add OPP tables and power domains to Tegra20 device-trees
ARM: tegra: Add OPP tables and power domains to Tegra30 device-trees
ARM: tegra: Add Memory Client resets to Tegra20 GR2D, GR3D and Host1x
ARM: tegra: Add Memory Client resets to Tegra30 GR2D, GR3D and Host1x
ARM: tegra20/30: Disable unused host1x hardware

.../bindings/clock/nvidia,tegra20-car.yaml | 37 +
.../display/tegra/nvidia,tegra20-host1x.txt | 53 +
.../bindings/media/nvidia,tegra-vde.txt | 64 -
.../bindings/media/nvidia,tegra-vde.yaml | 119 ++
.../boot/dts/tegra20-acer-a500-picasso.dts | 1 +
arch/arm/boot/dts/tegra20-colibri.dtsi | 3 +-
arch/arm/boot/dts/tegra20-harmony.dts | 3 +-
arch/arm/boot/dts/tegra20-paz00.dts | 1 +
.../arm/boot/dts/tegra20-peripherals-opp.dtsi | 941 +++++++++++
arch/arm/boot/dts/tegra20-seaboard.dts | 3 +-
arch/arm/boot/dts/tegra20-tamonten.dtsi | 3 +-
arch/arm/boot/dts/tegra20-trimslice.dts | 9 +
arch/arm/boot/dts/tegra20-ventana.dts | 1 +
arch/arm/boot/dts/tegra20.dtsi | 116 +-
.../tegra30-asus-nexus7-grouper-common.dtsi | 1 +
arch/arm/boot/dts/tegra30-beaver.dts | 1 +
arch/arm/boot/dts/tegra30-cardhu.dtsi | 1 +
arch/arm/boot/dts/tegra30-colibri.dtsi | 17 +-
arch/arm/boot/dts/tegra30-ouya.dts | 1 +
.../arm/boot/dts/tegra30-peripherals-opp.dtsi | 1412 +++++++++++++++++
arch/arm/boot/dts/tegra30.dtsi | 175 +-
drivers/bus/tegra-gmi.c | 52 +-
drivers/clk/tegra/Makefile | 1 +
drivers/clk/tegra/clk-device.c | 230 +++
drivers/clk/tegra/clk-pll.c | 2 +-
drivers/clk/tegra/clk-super.c | 2 +-
drivers/clk/tegra/clk-tegra20.c | 77 +-
drivers/clk/tegra/clk-tegra30.c | 116 +-
drivers/clk/tegra/clk.c | 75 +-
drivers/clk/tegra/clk.h | 2 +
drivers/gpu/drm/tegra/dc.c | 74 +
drivers/gpu/drm/tegra/dc.h | 2 +
drivers/gpu/drm/tegra/gr2d.c | 155 +-
drivers/gpu/drm/tegra/gr3d.c | 388 ++++-
drivers/gpu/drm/tegra/hdmi.c | 16 +-
drivers/gpu/drm/tegra/vic.c | 4 +
drivers/gpu/host1x/channel.c | 8 +
drivers/gpu/host1x/debug.c | 15 +
drivers/gpu/host1x/dev.c | 151 +-
drivers/gpu/host1x/dev.h | 3 +-
drivers/gpu/host1x/hw/channel_hw.c | 44 +-
drivers/gpu/host1x/intr.c | 3 -
drivers/gpu/host1x/syncpt.c | 5 +-
drivers/mmc/host/sdhci-tegra.c | 82 +-
drivers/mtd/nand/raw/tegra_nand.c | 55 +-
drivers/opp/core.c | 6 +-
drivers/pwm/pwm-tegra.c | 88 +-
drivers/soc/tegra/common.c | 4 +-
drivers/soc/tegra/fuse/fuse-tegra.c | 51 +-
drivers/soc/tegra/fuse/fuse-tegra20.c | 33 +-
drivers/soc/tegra/fuse/fuse.h | 1 +
drivers/soc/tegra/pmc.c | 27 +-
drivers/soc/tegra/regulators-tegra20.c | 99 ++
drivers/soc/tegra/regulators-tegra30.c | 122 ++
drivers/spi/spi-tegra20-slink.c | 10 +-
drivers/staging/media/tegra-vde/vde.c | 57 +-
drivers/usb/chipidea/ci_hdrc_tegra.c | 53 +-
include/linux/host1x.h | 1 +
include/linux/pm_opp.h | 8 +-
include/soc/tegra/common.h | 24 +
60 files changed, 4751 insertions(+), 357 deletions(-)
delete mode 100644 Documentation/devicetree/bindings/media/nvidia,tegra-vde.txt
create mode 100644 Documentation/devicetree/bindings/media/nvidia,tegra-vde.yaml
create mode 100644 drivers/clk/tegra/clk-device.c

--
2.32.0


2021-09-26 22:43:37

by Dmitry Osipenko

[permalink] [raw]
Subject: [PATCH v13 01/35] opp: Change type of dev_pm_opp_attach_genpd(names) argument

Elements of the 'names' array are not changed by the code, constify them
for consistency.

Signed-off-by: Dmitry Osipenko <[email protected]>
---
drivers/opp/core.c | 6 +++---
include/linux/pm_opp.h | 8 ++++----
2 files changed, 7 insertions(+), 7 deletions(-)

diff --git a/drivers/opp/core.c b/drivers/opp/core.c
index 04b4691a8aac..3057beabd370 100644
--- a/drivers/opp/core.c
+++ b/drivers/opp/core.c
@@ -2348,12 +2348,12 @@ static void _opp_detach_genpd(struct opp_table *opp_table)
* "required-opps" are added in DT.
*/
struct opp_table *dev_pm_opp_attach_genpd(struct device *dev,
- const char **names, struct device ***virt_devs)
+ const char * const *names, struct device ***virt_devs)
{
struct opp_table *opp_table;
struct device *virt_dev;
int index = 0, ret = -EINVAL;
- const char **name = names;
+ const char * const *name = names;

opp_table = _add_opp_table(dev, false);
if (IS_ERR(opp_table))
@@ -2457,7 +2457,7 @@ static void devm_pm_opp_detach_genpd(void *data)
*
* Return: 0 on success and errorno otherwise.
*/
-int devm_pm_opp_attach_genpd(struct device *dev, const char **names,
+int devm_pm_opp_attach_genpd(struct device *dev, const char * const *names,
struct device ***virt_devs)
{
struct opp_table *opp_table;
diff --git a/include/linux/pm_opp.h b/include/linux/pm_opp.h
index a95d6fdd20b6..879c138c7b8e 100644
--- a/include/linux/pm_opp.h
+++ b/include/linux/pm_opp.h
@@ -156,9 +156,9 @@ int devm_pm_opp_set_clkname(struct device *dev, const char *name);
struct opp_table *dev_pm_opp_register_set_opp_helper(struct device *dev, int (*set_opp)(struct dev_pm_set_opp_data *data));
void dev_pm_opp_unregister_set_opp_helper(struct opp_table *opp_table);
int devm_pm_opp_register_set_opp_helper(struct device *dev, int (*set_opp)(struct dev_pm_set_opp_data *data));
-struct opp_table *dev_pm_opp_attach_genpd(struct device *dev, const char **names, struct device ***virt_devs);
+struct opp_table *dev_pm_opp_attach_genpd(struct device *dev, const char * const *names, struct device ***virt_devs);
void dev_pm_opp_detach_genpd(struct opp_table *opp_table);
-int devm_pm_opp_attach_genpd(struct device *dev, const char **names, struct device ***virt_devs);
+int devm_pm_opp_attach_genpd(struct device *dev, const char * const *names, struct device ***virt_devs);
struct dev_pm_opp *dev_pm_opp_xlate_required_opp(struct opp_table *src_table, struct opp_table *dst_table, struct dev_pm_opp *src_opp);
int dev_pm_opp_xlate_performance_state(struct opp_table *src_table, struct opp_table *dst_table, unsigned int pstate);
int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq);
@@ -376,7 +376,7 @@ static inline int devm_pm_opp_set_clkname(struct device *dev, const char *name)
return -EOPNOTSUPP;
}

-static inline struct opp_table *dev_pm_opp_attach_genpd(struct device *dev, const char **names, struct device ***virt_devs)
+static inline struct opp_table *dev_pm_opp_attach_genpd(struct device *dev, const char * const *names, struct device ***virt_devs)
{
return ERR_PTR(-EOPNOTSUPP);
}
@@ -384,7 +384,7 @@ static inline struct opp_table *dev_pm_opp_attach_genpd(struct device *dev, cons
static inline void dev_pm_opp_detach_genpd(struct opp_table *opp_table) {}

static inline int devm_pm_opp_attach_genpd(struct device *dev,
- const char **names,
+ const char * const *names,
struct device ***virt_devs)
{
return -EOPNOTSUPP;
--
2.32.0

2021-09-26 22:43:39

by Dmitry Osipenko

[permalink] [raw]
Subject: [PATCH v13 02/35] soc/tegra: Add devm_tegra_core_dev_init_opp_table_common()

Only couple drivers need to get the -ENODEV error code and majority of
drivers need to explicitly initialize the performance state. Add new
common helper which sets up OPP table for these drivers.

Signed-off-by: Dmitry Osipenko <[email protected]>
---
include/soc/tegra/common.h | 24 ++++++++++++++++++++++++
1 file changed, 24 insertions(+)

diff --git a/include/soc/tegra/common.h b/include/soc/tegra/common.h
index af41ad80ec21..5b4a042f60fb 100644
--- a/include/soc/tegra/common.h
+++ b/include/soc/tegra/common.h
@@ -39,4 +39,28 @@ devm_tegra_core_dev_init_opp_table(struct device *dev,
}
#endif

+/*
+ * This function should be invoked with the enabled runtime PM of the device
+ * in order to initialize performance state properly. Most of Tegra devices
+ * are assumed to be suspended at a probe time and GENPD require RPM to be
+ * enabled to set up the rpm-resume state, otherwise device is active and
+ * performance state is applied immediately. Note that it will initialize
+ * OPP bandwidth if it's wired in a device-tree for this device, which is
+ * undesirable for a suspended device.
+ */
+static inline int
+devm_tegra_core_dev_init_opp_table_common(struct device *dev)
+{
+ struct tegra_core_opp_params opp_params = {};
+ int err;
+
+ opp_params.init_state = true;
+
+ err = devm_tegra_core_dev_init_opp_table(dev, &opp_params);
+ if (err != -ENODEV)
+ return err;
+
+ return 0;
+}
+
#endif /* __SOC_TEGRA_COMMON_H__ */
--
2.32.0

2021-09-26 22:43:47

by Dmitry Osipenko

[permalink] [raw]
Subject: [PATCH v13 04/35] soc/tegra: Don't print error message when OPPs not available

Previously we assumed that devm_tegra_core_dev_init_opp_table() will
be used only by drivers that will always have device with OPP table,
but this is not true anymore. For example now Tegra30 will have OPP table
for PWM, but Tegra20 not and both use the same driver. Hence let's not
print the error message about missing OPP table in the common helper,
we can print it elsewhere.

Signed-off-by: Dmitry Osipenko <[email protected]>
---
drivers/soc/tegra/common.c | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/drivers/soc/tegra/common.c b/drivers/soc/tegra/common.c
index cd33e99249c3..a42d4f98c078 100644
--- a/drivers/soc/tegra/common.c
+++ b/drivers/soc/tegra/common.c
@@ -111,9 +111,7 @@ int devm_tegra_core_dev_init_opp_table(struct device *dev,
*/
err = devm_pm_opp_of_add_table(dev);
if (err) {
- if (err == -ENODEV)
- dev_err_once(dev, "OPP table not found, please update device-tree\n");
- else
+ if (err != -ENODEV)
dev_err(dev, "failed to add OPP table: %d\n", err);

return err;
--
2.32.0

2021-09-26 22:44:04

by Dmitry Osipenko

[permalink] [raw]
Subject: [PATCH v13 07/35] dt-bindings: host1x: Document OPP and power domain properties

Document new DVFS OPP table and power domain properties of the Host1x bus
and devices sitting on the bus.

Reviewed-by: Rob Herring <[email protected]>
Signed-off-by: Dmitry Osipenko <[email protected]>
---
.../display/tegra/nvidia,tegra20-host1x.txt | 49 +++++++++++++++++++
1 file changed, 49 insertions(+)

diff --git a/Documentation/devicetree/bindings/display/tegra/nvidia,tegra20-host1x.txt b/Documentation/devicetree/bindings/display/tegra/nvidia,tegra20-host1x.txt
index 8a6d3e1ee306..62861a8fb5c6 100644
--- a/Documentation/devicetree/bindings/display/tegra/nvidia,tegra20-host1x.txt
+++ b/Documentation/devicetree/bindings/display/tegra/nvidia,tegra20-host1x.txt
@@ -20,6 +20,18 @@ Required properties:
- reset-names: Must include the following entries:
- host1x

+Optional properties:
+- operating-points-v2: See ../bindings/opp/opp.txt for details.
+ - power-domains: Phandle to HEG or core power domain.
+
+For each opp entry in 'operating-points-v2' table of host1x and its modules:
+- opp-supported-hw: One bitfield indicating:
+ On Tegra20: SoC process ID mask
+ On Tegra30+: SoC speedo ID mask
+
+ A bitwise AND is performed against the value and if any bit
+ matches, the OPP gets enabled.
+
Each host1x client module having to perform DMA through the Memory Controller
should have the interconnect endpoints set to the Memory Client and External
Memory respectively.
@@ -45,6 +57,8 @@ of the following host1x client modules:
- interconnect-names: Must include name of the interconnect path for each
interconnect entry. Consult TRM documentation for information about
available memory clients, see MEMORY CONTROLLER section.
+ - operating-points-v2: See ../bindings/opp/opp.txt for details.
+ - power-domains: Phandle to MPE power domain.

- vi: video input

@@ -128,6 +142,8 @@ of the following host1x client modules:
- interconnect-names: Must include name of the interconnect path for each
interconnect entry. Consult TRM documentation for information about
available memory clients, see MEMORY CONTROLLER section.
+ - operating-points-v2: See ../bindings/opp/opp.txt for details.
+ - power-domains: Phandle to VENC power domain.

- epp: encoder pre-processor

@@ -147,6 +163,8 @@ of the following host1x client modules:
- interconnect-names: Must include name of the interconnect path for each
interconnect entry. Consult TRM documentation for information about
available memory clients, see MEMORY CONTROLLER section.
+ - operating-points-v2: See ../bindings/opp/opp.txt for details.
+ - power-domains: Phandle to HEG or core power domain.

- isp: image signal processor

@@ -166,6 +184,7 @@ of the following host1x client modules:
- interconnect-names: Must include name of the interconnect path for each
interconnect entry. Consult TRM documentation for information about
available memory clients, see MEMORY CONTROLLER section.
+ - power-domains: Phandle to VENC or core power domain.

- gr2d: 2D graphics engine

@@ -185,6 +204,8 @@ of the following host1x client modules:
- interconnect-names: Must include name of the interconnect path for each
interconnect entry. Consult TRM documentation for information about
available memory clients, see MEMORY CONTROLLER section.
+ - operating-points-v2: See ../bindings/opp/opp.txt for details.
+ - power-domains: Phandle to HEG or core power domain.

- gr3d: 3D graphics engine

@@ -209,6 +230,8 @@ of the following host1x client modules:
- interconnect-names: Must include name of the interconnect path for each
interconnect entry. Consult TRM documentation for information about
available memory clients, see MEMORY CONTROLLER section.
+ - operating-points-v2: See ../bindings/opp/opp.txt for details.
+ - power-domains: Phandles to 3D or core power domain.

- dc: display controller

@@ -241,6 +264,8 @@ of the following host1x client modules:
- interconnect-names: Must include name of the interconnect path for each
interconnect entry. Consult TRM documentation for information about
available memory clients, see MEMORY CONTROLLER section.
+ - operating-points-v2: See ../bindings/opp/opp.txt for details.
+ - power-domains: Phandle to core power domain.

- hdmi: High Definition Multimedia Interface

@@ -267,6 +292,7 @@ of the following host1x client modules:
- nvidia,hpd-gpio: specifies a GPIO used for hotplug detection
- nvidia,edid: supplies a binary EDID blob
- nvidia,panel: phandle of a display panel
+ - operating-points-v2: See ../bindings/opp/opp.txt for details.

- tvo: TV encoder output

@@ -277,6 +303,10 @@ of the following host1x client modules:
- clocks: Must contain one entry, for the module clock.
See ../clocks/clock-bindings.txt for details.

+ Optional properties:
+ - operating-points-v2: See ../bindings/opp/opp.txt for details.
+ - power-domains: Phandle to core power domain.
+
- dsi: display serial interface

Required properties:
@@ -305,6 +335,7 @@ of the following host1x client modules:
- nvidia,panel: phandle of a display panel
- nvidia,ganged-mode: contains a phandle to a second DSI controller to gang
up with in order to support up to 8 data lanes
+ - operating-points-v2: See ../bindings/opp/opp.txt for details.

- sor: serial output resource

@@ -408,6 +439,8 @@ Example:
clocks = <&tegra_car TEGRA20_CLK_HOST1X>;
resets = <&tegra_car 28>;
reset-names = "host1x";
+ operating-points-v2 = <&dvfs_opp_table>;
+ power-domains = <&domain>;

#address-cells = <1>;
#size-cells = <1>;
@@ -421,6 +454,8 @@ Example:
clocks = <&tegra_car TEGRA20_CLK_MPE>;
resets = <&tegra_car 60>;
reset-names = "mpe";
+ operating-points-v2 = <&dvfs_opp_table>;
+ power-domains = <&domain>;
};

vi@54080000 {
@@ -429,6 +464,7 @@ Example:
interrupts = <GIC_SPI 69 IRQ_TYPE_LEVEL_HIGH>;
assigned-clocks = <&tegra_car TEGRA210_CLK_VI>;
assigned-clock-parents = <&tegra_car TEGRA210_CLK_PLL_C4_OUT0>;
+ operating-points-v2 = <&dvfs_opp_table>;

clocks = <&tegra_car TEGRA210_CLK_VI>;
power-domains = <&pd_venc>;
@@ -510,6 +546,8 @@ Example:
clocks = <&tegra_car TEGRA20_CLK_EPP>;
resets = <&tegra_car 19>;
reset-names = "epp";
+ operating-points-v2 = <&dvfs_opp_table>;
+ power-domains = <&domain>;
};

isp {
@@ -528,6 +566,8 @@ Example:
clocks = <&tegra_car TEGRA20_CLK_GR2D>;
resets = <&tegra_car 21>;
reset-names = "2d";
+ operating-points-v2 = <&dvfs_opp_table>;
+ power-domains = <&domain>;
};

gr3d {
@@ -536,6 +576,8 @@ Example:
clocks = <&tegra_car TEGRA20_CLK_GR3D>;
resets = <&tegra_car 24>;
reset-names = "3d";
+ operating-points-v2 = <&dvfs_opp_table>;
+ power-domains = <&domain>;
};

dc@54200000 {
@@ -547,6 +589,8 @@ Example:
clock-names = "dc", "parent";
resets = <&tegra_car 27>;
reset-names = "dc";
+ operating-points-v2 = <&dvfs_opp_table>;
+ power-domains = <&domain>;

interconnects = <&mc TEGRA20_MC_DISPLAY0A &emc>,
<&mc TEGRA20_MC_DISPLAY0B &emc>,
@@ -571,6 +615,8 @@ Example:
clock-names = "dc", "parent";
resets = <&tegra_car 26>;
reset-names = "dc";
+ operating-points-v2 = <&dvfs_opp_table>;
+ power-domains = <&domain>;

interconnects = <&mc TEGRA20_MC_DISPLAY0AB &emc>,
<&mc TEGRA20_MC_DISPLAY0BB &emc>,
@@ -596,6 +642,7 @@ Example:
resets = <&tegra_car 51>;
reset-names = "hdmi";
status = "disabled";
+ operating-points-v2 = <&dvfs_opp_table>;
};

tvo {
@@ -604,6 +651,7 @@ Example:
interrupts = <0 76 0x04>;
clocks = <&tegra_car TEGRA20_CLK_TVO>;
status = "disabled";
+ operating-points-v2 = <&dvfs_opp_table>;
};

dsi {
@@ -615,6 +663,7 @@ Example:
resets = <&tegra_car 48>;
reset-names = "dsi";
status = "disabled";
+ operating-points-v2 = <&dvfs_opp_table>;
};
};

--
2.32.0

2021-09-26 22:44:13

by Dmitry Osipenko

[permalink] [raw]
Subject: [PATCH v13 08/35] dt-bindings: host1x: Document Memory Client resets of Host1x, GR2D and GR3D

Memory Client should be blocked before hardware reset is asserted in order
to prevent memory corruption and hanging of memory controller.

Document Memory Client resets of Host1x, GR2D and GR3D hardware units.

Reviewed-by: Rob Herring <[email protected]>
Signed-off-by: Dmitry Osipenko <[email protected]>
---
.../bindings/display/tegra/nvidia,tegra20-host1x.txt | 4 ++++
1 file changed, 4 insertions(+)

diff --git a/Documentation/devicetree/bindings/display/tegra/nvidia,tegra20-host1x.txt b/Documentation/devicetree/bindings/display/tegra/nvidia,tegra20-host1x.txt
index 62861a8fb5c6..e61999ce54e9 100644
--- a/Documentation/devicetree/bindings/display/tegra/nvidia,tegra20-host1x.txt
+++ b/Documentation/devicetree/bindings/display/tegra/nvidia,tegra20-host1x.txt
@@ -19,6 +19,7 @@ Required properties:
See ../reset/reset.txt for details.
- reset-names: Must include the following entries:
- host1x
+ - mc

Optional properties:
- operating-points-v2: See ../bindings/opp/opp.txt for details.
@@ -198,6 +199,7 @@ of the following host1x client modules:
See ../reset/reset.txt for details.
- reset-names: Must include the following entries:
- 2d
+ - mc

Optional properties:
- interconnects: Must contain entry for the GR2D memory clients.
@@ -224,6 +226,8 @@ of the following host1x client modules:
- reset-names: Must include the following entries:
- 3d
- 3d2 (Only required on SoCs with two 3D clocks)
+ - mc
+ - mc2 (Only required on SoCs with two 3D clocks)

Optional properties:
- interconnects: Must contain entry for the GR3D memory clients.
--
2.32.0

2021-09-26 22:44:18

by Dmitry Osipenko

[permalink] [raw]
Subject: [PATCH v13 09/35] gpu: host1x: Add runtime PM and OPP support

Add runtime PM and OPP support to the Host1x driver. For the starter we
will keep host1x always-on because dynamic power management require a major
refactoring of the driver code since lot's of code paths are missing the
RPM handling and we're going to remove some of these paths in the future.

Tested-by: Peter Geis <[email protected]> # Ouya T30
Tested-by: Paul Fertser <[email protected]> # PAZ00 T20
Tested-by: Nicolas Chauvet <[email protected]> # PAZ00 T20 and TK1 T124
Tested-by: Matt Merhar <[email protected]> # Ouya T30
Signed-off-by: Dmitry Osipenko <[email protected]>
---
drivers/gpu/host1x/debug.c | 15 +++
drivers/gpu/host1x/dev.c | 151 +++++++++++++++++++++++------
drivers/gpu/host1x/dev.h | 3 +-
drivers/gpu/host1x/hw/channel_hw.c | 44 ++++-----
drivers/gpu/host1x/intr.c | 3 -
drivers/gpu/host1x/syncpt.c | 5 +-
6 files changed, 165 insertions(+), 56 deletions(-)

diff --git a/drivers/gpu/host1x/debug.c b/drivers/gpu/host1x/debug.c
index 8a14880c61bb..18d9c8d206e3 100644
--- a/drivers/gpu/host1x/debug.c
+++ b/drivers/gpu/host1x/debug.c
@@ -7,6 +7,7 @@
*/

#include <linux/debugfs.h>
+#include <linux/pm_runtime.h>
#include <linux/seq_file.h>
#include <linux/uaccess.h>

@@ -52,6 +53,11 @@ static int show_channel(struct host1x_channel *ch, void *data, bool show_fifo)
{
struct host1x *m = dev_get_drvdata(ch->dev->parent);
struct output *o = data;
+ int err;
+
+ err = pm_runtime_resume_and_get(m->dev);
+ if (err < 0)
+ return err;

mutex_lock(&ch->cdma.lock);
mutex_lock(&debug_lock);
@@ -64,6 +70,8 @@ static int show_channel(struct host1x_channel *ch, void *data, bool show_fifo)
mutex_unlock(&debug_lock);
mutex_unlock(&ch->cdma.lock);

+ pm_runtime_put(m->dev);
+
return 0;
}

@@ -71,9 +79,14 @@ static void show_syncpts(struct host1x *m, struct output *o)
{
struct list_head *pos;
unsigned int i;
+ int err;

host1x_debug_output(o, "---- syncpts ----\n");

+ err = pm_runtime_resume_and_get(m->dev);
+ if (err < 0)
+ return;
+
for (i = 0; i < host1x_syncpt_nb_pts(m); i++) {
u32 max = host1x_syncpt_read_max(m->syncpt + i);
u32 min = host1x_syncpt_load(m->syncpt + i);
@@ -101,6 +114,8 @@ static void show_syncpts(struct host1x *m, struct output *o)
base_val);
}

+ pm_runtime_put(m->dev);
+
host1x_debug_output(o, "\n");
}

diff --git a/drivers/gpu/host1x/dev.c b/drivers/gpu/host1x/dev.c
index fbb6447b8659..595b91099e3e 100644
--- a/drivers/gpu/host1x/dev.c
+++ b/drivers/gpu/host1x/dev.c
@@ -6,14 +6,18 @@
*/

#include <linux/clk.h>
+#include <linux/delay.h>
#include <linux/dma-mapping.h>
#include <linux/io.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/of.h>
+#include <linux/pm_runtime.h>
#include <linux/slab.h>

+#include <soc/tegra/common.h>
+
#define CREATE_TRACE_POINTS
#include <trace/events/host1x.h>
#undef CREATE_TRACE_POINTS
@@ -190,6 +194,9 @@ static void host1x_setup_sid_table(struct host1x *host)
const struct host1x_info *info = host->info;
unsigned int i;

+ if (!info->has_hypervisor)
+ return;
+
for (i = 0; i < info->num_sid_entries; i++) {
const struct host1x_sid_entry *entry = &info->sid_table[i];

@@ -347,6 +354,27 @@ static void host1x_iommu_exit(struct host1x *host)
}
}

+static int host1x_get_resets(struct host1x *host)
+{
+ int err;
+
+ host->resets[0].id = "mc";
+ host->resets[1].id = "host1x";
+ host->nresets = ARRAY_SIZE(host->resets);
+
+ err = devm_reset_control_bulk_get_optional_exclusive_released(
+ host->dev, host->nresets, host->resets);
+ if (err) {
+ dev_err(host->dev, "failed to get reset: %d\n", err);
+ return err;
+ }
+
+ if (WARN_ON(!host->resets[1].rstc))
+ return -ENOENT;
+
+ return 0;
+}
+
static int host1x_probe(struct platform_device *pdev)
{
struct host1x *host;
@@ -423,12 +451,9 @@ static int host1x_probe(struct platform_device *pdev)
return err;
}

- host->rst = devm_reset_control_get(&pdev->dev, "host1x");
- if (IS_ERR(host->rst)) {
- err = PTR_ERR(host->rst);
- dev_err(&pdev->dev, "failed to get reset: %d\n", err);
+ err = host1x_get_resets(host);
+ if (err)
return err;
- }

err = host1x_iommu_init(host);
if (err < 0) {
@@ -443,22 +468,10 @@ static int host1x_probe(struct platform_device *pdev)
goto iommu_exit;
}

- err = clk_prepare_enable(host->clk);
- if (err < 0) {
- dev_err(&pdev->dev, "failed to enable clock\n");
- goto free_channels;
- }
-
- err = reset_control_deassert(host->rst);
- if (err < 0) {
- dev_err(&pdev->dev, "failed to deassert reset: %d\n", err);
- goto unprepare_disable;
- }
-
err = host1x_syncpt_init(host);
if (err) {
dev_err(&pdev->dev, "failed to initialize syncpts\n");
- goto reset_assert;
+ goto free_channels;
}

err = host1x_intr_init(host, syncpt_irq);
@@ -467,10 +480,18 @@ static int host1x_probe(struct platform_device *pdev)
goto deinit_syncpt;
}

- host1x_debug_init(host);
+ pm_runtime_enable(&pdev->dev);
+
+ err = devm_tegra_core_dev_init_opp_table_common(&pdev->dev);
+ if (err)
+ goto pm_disable;

- if (host->info->has_hypervisor)
- host1x_setup_sid_table(host);
+ /* the driver's code isn't ready yet for the dynamic RPM */
+ err = pm_runtime_resume_and_get(&pdev->dev);
+ if (err)
+ goto pm_disable;
+
+ host1x_debug_init(host);

err = host1x_register(host);
if (err < 0)
@@ -486,13 +507,14 @@ static int host1x_probe(struct platform_device *pdev)
host1x_unregister(host);
deinit_debugfs:
host1x_debug_deinit(host);
+
+ pm_runtime_put(&pdev->dev);
+pm_disable:
+ pm_runtime_disable(&pdev->dev);
+
host1x_intr_deinit(host);
deinit_syncpt:
host1x_syncpt_deinit(host);
-reset_assert:
- reset_control_assert(host->rst);
-unprepare_disable:
- clk_disable_unprepare(host->clk);
free_channels:
host1x_channel_list_free(&host->channel_list);
iommu_exit:
@@ -507,19 +529,94 @@ static int host1x_remove(struct platform_device *pdev)

host1x_unregister(host);
host1x_debug_deinit(host);
+
+ pm_runtime_put(&pdev->dev);
+ pm_runtime_disable(&pdev->dev);
+
host1x_intr_deinit(host);
host1x_syncpt_deinit(host);
- reset_control_assert(host->rst);
- clk_disable_unprepare(host->clk);
host1x_iommu_exit(host);

return 0;
}

+static int __maybe_unused host1x_runtime_suspend(struct device *dev)
+{
+ struct host1x *host = dev_get_drvdata(dev);
+ int err;
+
+ host1x_intr_stop(host);
+ host1x_syncpt_save(host);
+
+ err = reset_control_bulk_assert(host->nresets, host->resets);
+ if (err) {
+ dev_err(dev, "failed to assert reset: %d\n", err);
+ goto resume_host1x;
+ }
+
+ usleep_range(1000, 2000);
+
+ clk_disable_unprepare(host->clk);
+ reset_control_bulk_release(host->nresets, host->resets);
+
+ return 0;
+
+resume_host1x:
+ host1x_setup_sid_table(host);
+ host1x_syncpt_restore(host);
+ host1x_intr_start(host);
+
+ return err;
+}
+
+static int __maybe_unused host1x_runtime_resume(struct device *dev)
+{
+ struct host1x *host = dev_get_drvdata(dev);
+ int err;
+
+ err = reset_control_bulk_acquire(host->nresets, host->resets);
+ if (err) {
+ dev_err(dev, "failed to acquire reset: %d\n", err);
+ return err;
+ }
+
+ err = clk_prepare_enable(host->clk);
+ if (err) {
+ dev_err(dev, "failed to enable clock: %d\n", err);
+ goto release_reset;
+ }
+
+ err = reset_control_bulk_deassert(host->nresets, host->resets);
+ if (err < 0) {
+ dev_err(dev, "failed to deassert reset: %d\n", err);
+ goto disable_clk;
+ }
+
+ host1x_setup_sid_table(host);
+ host1x_syncpt_restore(host);
+ host1x_intr_start(host);
+
+ return 0;
+
+disable_clk:
+ clk_disable_unprepare(host->clk);
+release_reset:
+ reset_control_bulk_release(host->nresets, host->resets);
+
+ return err;
+}
+
+static const struct dev_pm_ops host1x_pm = {
+ SET_RUNTIME_PM_OPS(host1x_runtime_suspend, host1x_runtime_resume,
+ NULL)
+ /* TODO: add system suspend-resume once driver will be ready for that */
+};
+
static struct platform_driver tegra_host1x_driver = {
.driver = {
.name = "tegra-host1x",
.of_match_table = host1x_of_match,
+ .pm = &host1x_pm,
},
.probe = host1x_probe,
.remove = host1x_remove,
diff --git a/drivers/gpu/host1x/dev.h b/drivers/gpu/host1x/dev.h
index fa6d4bc46e98..41a7a63514c4 100644
--- a/drivers/gpu/host1x/dev.h
+++ b/drivers/gpu/host1x/dev.h
@@ -118,7 +118,8 @@ struct host1x {
struct host1x_syncpt_base *bases;
struct device *dev;
struct clk *clk;
- struct reset_control *rst;
+ struct reset_control_bulk_data resets[2];
+ unsigned int nresets;

struct iommu_group *group;
struct iommu_domain *domain;
diff --git a/drivers/gpu/host1x/hw/channel_hw.c b/drivers/gpu/host1x/hw/channel_hw.c
index 1999780a7203..6b40e9af1e88 100644
--- a/drivers/gpu/host1x/hw/channel_hw.c
+++ b/drivers/gpu/host1x/hw/channel_hw.c
@@ -159,6 +159,27 @@ static void host1x_channel_set_streamid(struct host1x_channel *channel)
#endif
}

+static void host1x_enable_gather_filter(struct host1x_channel *ch)
+{
+#if HOST1X_HW >= 6
+ struct host1x *host = dev_get_drvdata(ch->dev->parent);
+ u32 val;
+
+ if (!host->hv_regs)
+ return;
+
+ val = host1x_hypervisor_readl(
+ host, HOST1X_HV_CH_KERNEL_FILTER_GBUFFER(ch->id / 32));
+ val |= BIT(ch->id % 32);
+ host1x_hypervisor_writel(
+ host, val, HOST1X_HV_CH_KERNEL_FILTER_GBUFFER(ch->id / 32));
+#elif HOST1X_HW >= 4
+ host1x_ch_writel(ch,
+ HOST1X_CHANNEL_CHANNELCTRL_KERNEL_FILTER_GBUFFER(1),
+ HOST1X_CHANNEL_CHANNELCTRL);
+#endif
+}
+
static int channel_submit(struct host1x_job *job)
{
struct host1x_channel *ch = job->channel;
@@ -190,6 +211,7 @@ static int channel_submit(struct host1x_job *job)
}

host1x_channel_set_streamid(ch);
+ host1x_enable_gather_filter(ch);

/* begin a CDMA submit */
err = host1x_cdma_begin(&ch->cdma, job);
@@ -249,27 +271,6 @@ static int channel_submit(struct host1x_job *job)
return err;
}

-static void enable_gather_filter(struct host1x *host,
- struct host1x_channel *ch)
-{
-#if HOST1X_HW >= 6
- u32 val;
-
- if (!host->hv_regs)
- return;
-
- val = host1x_hypervisor_readl(
- host, HOST1X_HV_CH_KERNEL_FILTER_GBUFFER(ch->id / 32));
- val |= BIT(ch->id % 32);
- host1x_hypervisor_writel(
- host, val, HOST1X_HV_CH_KERNEL_FILTER_GBUFFER(ch->id / 32));
-#elif HOST1X_HW >= 4
- host1x_ch_writel(ch,
- HOST1X_CHANNEL_CHANNELCTRL_KERNEL_FILTER_GBUFFER(1),
- HOST1X_CHANNEL_CHANNELCTRL);
-#endif
-}
-
static int host1x_channel_init(struct host1x_channel *ch, struct host1x *dev,
unsigned int index)
{
@@ -278,7 +279,6 @@ static int host1x_channel_init(struct host1x_channel *ch, struct host1x *dev,
#else
ch->regs = dev->regs + index * 0x100;
#endif
- enable_gather_filter(dev, ch);
return 0;
}

diff --git a/drivers/gpu/host1x/intr.c b/drivers/gpu/host1x/intr.c
index 45b6be927ec4..965ba21818b1 100644
--- a/drivers/gpu/host1x/intr.c
+++ b/drivers/gpu/host1x/intr.c
@@ -297,14 +297,11 @@ int host1x_intr_init(struct host1x *host, unsigned int irq_sync)
"host1x_sp_%02u", id);
}

- host1x_intr_start(host);
-
return 0;
}

void host1x_intr_deinit(struct host1x *host)
{
- host1x_intr_stop(host);
}

void host1x_intr_start(struct host1x *host)
diff --git a/drivers/gpu/host1x/syncpt.c b/drivers/gpu/host1x/syncpt.c
index d198a10848c6..e08e331e46ae 100644
--- a/drivers/gpu/host1x/syncpt.c
+++ b/drivers/gpu/host1x/syncpt.c
@@ -143,6 +143,8 @@ void host1x_syncpt_restore(struct host1x *host)
for (i = 0; i < host1x_syncpt_nb_bases(host); i++)
host1x_hw_syncpt_restore_wait_base(host, sp_base + i);

+ host1x_hw_syncpt_enable_protection(host);
+
wmb();
}

@@ -366,9 +368,6 @@ int host1x_syncpt_init(struct host1x *host)
host->syncpt = syncpt;
host->bases = bases;

- host1x_syncpt_restore(host);
- host1x_hw_syncpt_enable_protection(host);
-
/* Allocate sync point to use for clearing waits for expired fences */
host->nop_sp = host1x_syncpt_alloc(host, 0, "reserved-nop");
if (!host->nop_sp)
--
2.32.0

2021-09-26 22:44:31

by Dmitry Osipenko

[permalink] [raw]
Subject: [PATCH v13 21/35] spi: tegra20-slink: Add OPP support

The SPI on Tegra belongs to the core power domain and we're going to
enable GENPD support for the core domain. Now SPI driver must use OPP
API for driving the controller's clock rate because OPP API takes care
of reconfiguring the domain's performance state in accordance to the
rate. Add OPP support to the driver.

Acked-by: Mark Brown <[email protected]>
Signed-off-by: Dmitry Osipenko <[email protected]>
---
drivers/spi/spi-tegra20-slink.c | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)

diff --git a/drivers/spi/spi-tegra20-slink.c b/drivers/spi/spi-tegra20-slink.c
index 3226c4e1c7c0..eea1853010e8 100644
--- a/drivers/spi/spi-tegra20-slink.c
+++ b/drivers/spi/spi-tegra20-slink.c
@@ -18,12 +18,15 @@
#include <linux/kthread.h>
#include <linux/module.h>
#include <linux/platform_device.h>
+#include <linux/pm_opp.h>
#include <linux/pm_runtime.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/reset.h>
#include <linux/spi/spi.h>

+#include <soc/tegra/common.h>
+
#define SLINK_COMMAND 0x000
#define SLINK_BIT_LENGTH(x) (((x) & 0x1f) << 0)
#define SLINK_WORD_SIZE(x) (((x) & 0x1f) << 5)
@@ -680,7 +683,7 @@ static int tegra_slink_start_transfer_one(struct spi_device *spi,
bits_per_word = t->bits_per_word;
speed = t->speed_hz;
if (speed != tspi->cur_speed) {
- clk_set_rate(tspi->clk, speed * 4);
+ dev_pm_opp_set_rate(tspi->dev, speed * 4);
tspi->cur_speed = speed;
}

@@ -1082,6 +1085,11 @@ static int tegra_slink_probe(struct platform_device *pdev)
init_completion(&tspi->xfer_completion);

pm_runtime_enable(&pdev->dev);
+
+ ret = devm_tegra_core_dev_init_opp_table_common(&pdev->dev);
+ if (ret)
+ goto exit_pm_disable;
+
ret = pm_runtime_resume_and_get(&pdev->dev);
if (ret) {
dev_err(&pdev->dev, "pm runtime get failed, e = %d\n", ret);
--
2.32.0

2021-09-26 22:44:38

by Dmitry Osipenko

[permalink] [raw]
Subject: [PATCH v13 23/35] media: dt: bindings: tegra-vde: Document OPP and power domain

Document new OPP table and power domain properties of the video decoder
hardware.

Reviewed-by: Rob Herring <[email protected]>
Acked-by: Hans Verkuil <[email protected]>
Signed-off-by: Dmitry Osipenko <[email protected]>
---
.../devicetree/bindings/media/nvidia,tegra-vde.yaml | 12 ++++++++++++
1 file changed, 12 insertions(+)

diff --git a/Documentation/devicetree/bindings/media/nvidia,tegra-vde.yaml b/Documentation/devicetree/bindings/media/nvidia,tegra-vde.yaml
index 3b6c1f031e04..0b7d4d815707 100644
--- a/Documentation/devicetree/bindings/media/nvidia,tegra-vde.yaml
+++ b/Documentation/devicetree/bindings/media/nvidia,tegra-vde.yaml
@@ -68,6 +68,16 @@ properties:
description:
Phandle of the SRAM MMIO node.

+ operating-points-v2:
+ description:
+ Should contain freqs and voltages and opp-supported-hw property,
+ which is a bitfield indicating SoC speedo or process ID mask.
+
+ power-domains:
+ maxItems: 1
+ description:
+ Phandle to the SoC core power domain.
+
required:
- compatible
- reg
@@ -104,4 +114,6 @@ examples:
reset-names = "vde", "mc";
resets = <&rst 61>, <&mem 13>;
iommus = <&mem 15>;
+ operating-points-v2 = <&dvfs_opp_table>;
+ power-domains = <&domain>;
};
--
2.32.0

2021-09-26 22:44:49

by Dmitry Osipenko

[permalink] [raw]
Subject: [PATCH v13 25/35] soc/tegra: fuse: Reset hardware

The FUSE controller is enabled at a boot time. Reset it in order to put
hardware and clock into clean and disabled state.

Signed-off-by: Dmitry Osipenko <[email protected]>
---
drivers/soc/tegra/fuse/fuse-tegra.c | 25 +++++++++++++++++++++++++
drivers/soc/tegra/fuse/fuse.h | 1 +
2 files changed, 26 insertions(+)

diff --git a/drivers/soc/tegra/fuse/fuse-tegra.c b/drivers/soc/tegra/fuse/fuse-tegra.c
index f2151815db58..cc032729a143 100644
--- a/drivers/soc/tegra/fuse/fuse-tegra.c
+++ b/drivers/soc/tegra/fuse/fuse-tegra.c
@@ -14,6 +14,7 @@
#include <linux/of_address.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
+#include <linux/reset.h>
#include <linux/slab.h>
#include <linux/sys_soc.h>

@@ -243,6 +244,30 @@ static int tegra_fuse_probe(struct platform_device *pdev)
goto restore;
}

+ fuse->rst = devm_reset_control_get_optional(&pdev->dev, "fuse");
+ if (IS_ERR(fuse->rst)) {
+ err = PTR_ERR(fuse->rst);
+ dev_err(&pdev->dev, "failed to get FUSE reset: %pe\n",
+ fuse->rst);
+ goto restore;
+ }
+
+ /*
+ * FUSE clock is enabled at a boot time, hence this resume/suspend
+ * disables the clock besides the h/w resetting.
+ */
+ err = pm_runtime_resume_and_get(&pdev->dev);
+ if (err)
+ goto restore;
+
+ err = reset_control_reset(fuse->rst);
+ pm_runtime_put(&pdev->dev);
+
+ if (err < 0) {
+ dev_err(&pdev->dev, "failed to reset FUSE: %d\n", err);
+ goto restore;
+ }
+
/* release the early I/O memory mapping */
iounmap(base);

diff --git a/drivers/soc/tegra/fuse/fuse.h b/drivers/soc/tegra/fuse/fuse.h
index de58feba0435..1b719d85bd04 100644
--- a/drivers/soc/tegra/fuse/fuse.h
+++ b/drivers/soc/tegra/fuse/fuse.h
@@ -43,6 +43,7 @@ struct tegra_fuse {
void __iomem *base;
phys_addr_t phys;
struct clk *clk;
+ struct reset_control *rst;

u32 (*read_early)(struct tegra_fuse *fuse, unsigned int offset);
u32 (*read)(struct tegra_fuse *fuse, unsigned int offset);
--
2.32.0

2021-09-26 22:44:49

by Dmitry Osipenko

[permalink] [raw]
Subject: [PATCH v13 22/35] media: dt: bindings: tegra-vde: Convert to schema

Convert NVIDIA Tegra video decoder binding to schema.

Reviewed-by: Rob Herring <[email protected]>
Acked-by: Hans Verkuil <[email protected]>
Signed-off-by: Dmitry Osipenko <[email protected]>
---
.../bindings/media/nvidia,tegra-vde.txt | 64 -----------
.../bindings/media/nvidia,tegra-vde.yaml | 107 ++++++++++++++++++
2 files changed, 107 insertions(+), 64 deletions(-)
delete mode 100644 Documentation/devicetree/bindings/media/nvidia,tegra-vde.txt
create mode 100644 Documentation/devicetree/bindings/media/nvidia,tegra-vde.yaml

diff --git a/Documentation/devicetree/bindings/media/nvidia,tegra-vde.txt b/Documentation/devicetree/bindings/media/nvidia,tegra-vde.txt
deleted file mode 100644
index 602169b8aa19..000000000000
--- a/Documentation/devicetree/bindings/media/nvidia,tegra-vde.txt
+++ /dev/null
@@ -1,64 +0,0 @@
-NVIDIA Tegra Video Decoder Engine
-
-Required properties:
-- compatible : Must contain one of the following values:
- - "nvidia,tegra20-vde"
- - "nvidia,tegra30-vde"
- - "nvidia,tegra114-vde"
- - "nvidia,tegra124-vde"
- - "nvidia,tegra132-vde"
-- reg : Must contain an entry for each entry in reg-names.
-- reg-names : Must include the following entries:
- - sxe
- - bsev
- - mbe
- - ppe
- - mce
- - tfe
- - ppb
- - vdma
- - frameid
-- iram : Must contain phandle to the mmio-sram device node that represents
- IRAM region used by VDE.
-- interrupts : Must contain an entry for each entry in interrupt-names.
-- interrupt-names : Must include the following entries:
- - sync-token
- - bsev
- - sxe
-- clocks : Must include the following entries:
- - vde
-- resets : Must contain an entry for each entry in reset-names.
-- reset-names : Should include the following entries:
- - vde
-
-Optional properties:
-- resets : Must contain an entry for each entry in reset-names.
-- reset-names : Must include the following entries:
- - mc
-- iommus: Must contain phandle to the IOMMU device node.
-
-Example:
-
-video-codec@6001a000 {
- compatible = "nvidia,tegra20-vde";
- reg = <0x6001a000 0x1000 /* Syntax Engine */
- 0x6001b000 0x1000 /* Video Bitstream Engine */
- 0x6001c000 0x100 /* Macroblock Engine */
- 0x6001c200 0x100 /* Post-processing Engine */
- 0x6001c400 0x100 /* Motion Compensation Engine */
- 0x6001c600 0x100 /* Transform Engine */
- 0x6001c800 0x100 /* Pixel prediction block */
- 0x6001ca00 0x100 /* Video DMA */
- 0x6001d800 0x300 /* Video frame controls */>;
- reg-names = "sxe", "bsev", "mbe", "ppe", "mce",
- "tfe", "ppb", "vdma", "frameid";
- iram = <&vde_pool>; /* IRAM region */
- interrupts = <GIC_SPI 9 IRQ_TYPE_LEVEL_HIGH>, /* Sync token interrupt */
- <GIC_SPI 10 IRQ_TYPE_LEVEL_HIGH>, /* BSE-V interrupt */
- <GIC_SPI 12 IRQ_TYPE_LEVEL_HIGH>; /* SXE interrupt */
- interrupt-names = "sync-token", "bsev", "sxe";
- clocks = <&tegra_car TEGRA20_CLK_VDE>;
- reset-names = "vde", "mc";
- resets = <&tegra_car 61>, <&mc TEGRA20_MC_RESET_VDE>;
- iommus = <&mc TEGRA_SWGROUP_VDE>;
-};
diff --git a/Documentation/devicetree/bindings/media/nvidia,tegra-vde.yaml b/Documentation/devicetree/bindings/media/nvidia,tegra-vde.yaml
new file mode 100644
index 000000000000..3b6c1f031e04
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/nvidia,tegra-vde.yaml
@@ -0,0 +1,107 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/media/nvidia,tegra-vde.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: NVIDIA Tegra Video Decoder Engine
+
+maintainers:
+ - Dmitry Osipenko <[email protected]>
+ - Jon Hunter <[email protected]>
+ - Thierry Reding <[email protected]>
+
+properties:
+ compatible:
+ oneOf:
+ - items:
+ - enum:
+ - nvidia,tegra132-vde
+ - nvidia,tegra124-vde
+ - nvidia,tegra114-vde
+ - nvidia,tegra30-vde
+ - enum:
+ - nvidia,tegra20-vde
+ - items:
+ - const: nvidia,tegra20-vde
+
+ reg:
+ maxItems: 9
+
+ reg-names:
+ items:
+ - const: sxe
+ - const: bsev
+ - const: mbe
+ - const: ppe
+ - const: mce
+ - const: tfe
+ - const: ppb
+ - const: vdma
+ - const: frameid
+
+ clocks:
+ maxItems: 1
+
+ resets:
+ maxItems: 2
+
+ reset-names:
+ items:
+ - const: vde
+ - const: mc
+
+ interrupts:
+ maxItems: 3
+
+ interrupt-names:
+ items:
+ - const: sync-token
+ - const: bsev
+ - const: sxe
+
+ iommus:
+ maxItems: 1
+
+ iram:
+ $ref: /schemas/types.yaml#/definitions/phandle
+ description:
+ Phandle of the SRAM MMIO node.
+
+required:
+ - compatible
+ - reg
+ - reg-names
+ - clocks
+ - resets
+ - reset-names
+ - interrupts
+ - interrupt-names
+
+additionalProperties: false
+
+examples:
+ - |
+ video-codec@6001a000 {
+ compatible = "nvidia,tegra20-vde";
+ reg = <0x6001a000 0x1000>, /* Syntax Engine */
+ <0x6001b000 0x1000>, /* Video Bitstream Engine */
+ <0x6001c000 0x100>, /* Macroblock Engine */
+ <0x6001c200 0x100>, /* Post-processing Engine */
+ <0x6001c400 0x100>, /* Motion Compensation Engine */
+ <0x6001c600 0x100>, /* Transform Engine */
+ <0x6001c800 0x100>, /* Pixel prediction block */
+ <0x6001ca00 0x100>, /* Video DMA */
+ <0x6001d800 0x300>; /* Video frame controls */
+ reg-names = "sxe", "bsev", "mbe", "ppe", "mce",
+ "tfe", "ppb", "vdma", "frameid";
+ iram = <&iram>; /* IRAM MMIO region */
+ interrupts = <0 9 4>, /* Sync token */
+ <0 10 4>, /* BSE-V */
+ <0 12 4>; /* SXE */
+ interrupt-names = "sync-token", "bsev", "sxe";
+ clocks = <&clk 61>;
+ reset-names = "vde", "mc";
+ resets = <&rst 61>, <&mem 13>;
+ iommus = <&mem 15>;
+ };
--
2.32.0

2021-09-26 22:44:51

by Dmitry Osipenko

[permalink] [raw]
Subject: [PATCH v13 24/35] media: staging: tegra-vde: Support generic power domain

Currently driver supports legacy power domain API, this patch adds generic
power domain support. This allows us to utilize a modern GENPD API for
newer device-trees.

Tested-by: Peter Geis <[email protected]> # Ouya T30
Tested-by: Paul Fertser <[email protected]> # PAZ00 T20
Tested-by: Nicolas Chauvet <[email protected]> # PAZ00 T20 and TK1 T124
Tested-by: Matt Merhar <[email protected]> # Ouya T30
Acked-by: Hans Verkuil <[email protected]>
Signed-off-by: Dmitry Osipenko <[email protected]>
---
drivers/staging/media/tegra-vde/vde.c | 57 +++++++++++++++++++++------
1 file changed, 46 insertions(+), 11 deletions(-)

diff --git a/drivers/staging/media/tegra-vde/vde.c b/drivers/staging/media/tegra-vde/vde.c
index ed4c1250b303..bb3079a2c0b5 100644
--- a/drivers/staging/media/tegra-vde/vde.c
+++ b/drivers/staging/media/tegra-vde/vde.c
@@ -20,6 +20,7 @@
#include <linux/slab.h>
#include <linux/uaccess.h>

+#include <soc/tegra/common.h>
#include <soc/tegra/pmc.h>

#include "uapi.h"
@@ -920,13 +921,17 @@ static __maybe_unused int tegra_vde_runtime_suspend(struct device *dev)
struct tegra_vde *vde = dev_get_drvdata(dev);
int err;

- err = tegra_powergate_power_off(TEGRA_POWERGATE_VDEC);
- if (err) {
- dev_err(dev, "Failed to power down HW: %d\n", err);
- return err;
+ if (!dev->pm_domain) {
+ err = tegra_powergate_power_off(TEGRA_POWERGATE_VDEC);
+ if (err) {
+ dev_err(dev, "Failed to power down HW: %d\n", err);
+ return err;
+ }
}

clk_disable_unprepare(vde->clk);
+ reset_control_release(vde->rst);
+ reset_control_release(vde->rst_mc);

return 0;
}
@@ -936,14 +941,41 @@ static __maybe_unused int tegra_vde_runtime_resume(struct device *dev)
struct tegra_vde *vde = dev_get_drvdata(dev);
int err;

- err = tegra_powergate_sequence_power_up(TEGRA_POWERGATE_VDEC,
- vde->clk, vde->rst);
+ err = reset_control_acquire(vde->rst_mc);
if (err) {
- dev_err(dev, "Failed to power up HW : %d\n", err);
+ dev_err(dev, "Failed to acquire mc reset: %d\n", err);
return err;
}

+ err = reset_control_acquire(vde->rst);
+ if (err) {
+ dev_err(dev, "Failed to acquire reset: %d\n", err);
+ goto release_mc_reset;
+ }
+
+ if (!dev->pm_domain) {
+ err = tegra_powergate_sequence_power_up(TEGRA_POWERGATE_VDEC,
+ vde->clk, vde->rst);
+ if (err) {
+ dev_err(dev, "Failed to power up HW : %d\n", err);
+ goto release_reset;
+ }
+ }
+
+ err = clk_prepare_enable(vde->clk);
+ if (err) {
+ dev_err(dev, "Failed to enable clock: %d\n", err);
+ goto release_reset;
+ }
+
return 0;
+
+release_reset:
+ reset_control_release(vde->rst);
+release_mc_reset:
+ reset_control_release(vde->rst_mc);
+
+ return err;
}

static int tegra_vde_probe(struct platform_device *pdev)
@@ -1001,14 +1033,14 @@ static int tegra_vde_probe(struct platform_device *pdev)
return err;
}

- vde->rst = devm_reset_control_get(dev, NULL);
+ vde->rst = devm_reset_control_get_exclusive_released(dev, NULL);
if (IS_ERR(vde->rst)) {
err = PTR_ERR(vde->rst);
dev_err(dev, "Could not get VDE reset %d\n", err);
return err;
}

- vde->rst_mc = devm_reset_control_get_optional(dev, "mc");
+ vde->rst_mc = devm_reset_control_get_optional_exclusive_released(dev, "mc");
if (IS_ERR(vde->rst_mc)) {
err = PTR_ERR(vde->rst_mc);
dev_err(dev, "Could not get MC reset %d\n", err);
@@ -1066,6 +1098,10 @@ static int tegra_vde_probe(struct platform_device *pdev)
pm_runtime_use_autosuspend(dev);
pm_runtime_set_autosuspend_delay(dev, 300);

+ err = devm_tegra_core_dev_init_opp_table_common(dev);
+ if (err)
+ goto err_pm_runtime;
+
/*
* VDE partition may be left ON after bootloader, hence let's
* power-cycle it in order to put hardware into a predictable lower
@@ -1133,8 +1169,7 @@ static void tegra_vde_shutdown(struct platform_device *pdev)
* On some devices bootloader isn't ready to a power-gated VDE on
* a warm-reboot, machine will hang in that case.
*/
- if (pm_runtime_status_suspended(&pdev->dev))
- tegra_vde_runtime_resume(&pdev->dev);
+ pm_runtime_get_sync(&pdev->dev);
}

static __maybe_unused int tegra_vde_pm_suspend(struct device *dev)
--
2.32.0

2021-09-26 22:44:52

by Dmitry Osipenko

[permalink] [raw]
Subject: [PATCH v13 03/35] soc/tegra: pmc: Disable PMC state syncing

Disable PMC state syncing in order to ensure that we won't break older
kernels once device-trees will be updated with the addition of the power
domains. This also allows to apply device-tree PM patches independently
from the driver patches.

Signed-off-by: Dmitry Osipenko <[email protected]>
---
drivers/soc/tegra/pmc.c | 17 +++++++++++++++++
1 file changed, 17 insertions(+)

diff --git a/drivers/soc/tegra/pmc.c b/drivers/soc/tegra/pmc.c
index 50091c4ec948..fb8faf7b226a 100644
--- a/drivers/soc/tegra/pmc.c
+++ b/drivers/soc/tegra/pmc.c
@@ -360,6 +360,7 @@ struct tegra_pmc_soc {
unsigned int num_pmc_clks;
bool has_blink_output;
bool has_usb_sleepwalk;
+ bool supports_core_domain;
};

/**
@@ -3041,6 +3042,7 @@ static void tegra20_pmc_setup_irq_polarity(struct tegra_pmc *pmc,
}

static const struct tegra_pmc_soc tegra20_pmc_soc = {
+ .supports_core_domain = false,
.num_powergates = ARRAY_SIZE(tegra20_powergates),
.powergates = tegra20_powergates,
.num_cpu_powergates = 0,
@@ -3101,6 +3103,7 @@ static const char * const tegra30_reset_sources[] = {
};

static const struct tegra_pmc_soc tegra30_pmc_soc = {
+ .supports_core_domain = false,
.num_powergates = ARRAY_SIZE(tegra30_powergates),
.powergates = tegra30_powergates,
.num_cpu_powergates = ARRAY_SIZE(tegra30_cpu_powergates),
@@ -3157,6 +3160,7 @@ static const u8 tegra114_cpu_powergates[] = {
};

static const struct tegra_pmc_soc tegra114_pmc_soc = {
+ .supports_core_domain = false,
.num_powergates = ARRAY_SIZE(tegra114_powergates),
.powergates = tegra114_powergates,
.num_cpu_powergates = ARRAY_SIZE(tegra114_cpu_powergates),
@@ -3273,6 +3277,7 @@ static const struct pinctrl_pin_desc tegra124_pin_descs[] = {
};

static const struct tegra_pmc_soc tegra124_pmc_soc = {
+ .supports_core_domain = false,
.num_powergates = ARRAY_SIZE(tegra124_powergates),
.powergates = tegra124_powergates,
.num_cpu_powergates = ARRAY_SIZE(tegra124_cpu_powergates),
@@ -3398,6 +3403,7 @@ static const struct tegra_wake_event tegra210_wake_events[] = {
};

static const struct tegra_pmc_soc tegra210_pmc_soc = {
+ .supports_core_domain = false,
.num_powergates = ARRAY_SIZE(tegra210_powergates),
.powergates = tegra210_powergates,
.num_cpu_powergates = ARRAY_SIZE(tegra210_cpu_powergates),
@@ -3555,6 +3561,7 @@ static const struct tegra_wake_event tegra186_wake_events[] = {
};

static const struct tegra_pmc_soc tegra186_pmc_soc = {
+ .supports_core_domain = false,
.num_powergates = 0,
.powergates = NULL,
.num_cpu_powergates = 0,
@@ -3689,6 +3696,7 @@ static const struct tegra_wake_event tegra194_wake_events[] = {
};

static const struct tegra_pmc_soc tegra194_pmc_soc = {
+ .supports_core_domain = false,
.num_powergates = 0,
.powergates = NULL,
.num_cpu_powergates = 0,
@@ -3757,6 +3765,7 @@ static const char * const tegra234_reset_sources[] = {
};

static const struct tegra_pmc_soc tegra234_pmc_soc = {
+ .supports_core_domain = false,
.num_powergates = 0,
.powergates = NULL,
.num_cpu_powergates = 0,
@@ -3803,6 +3812,14 @@ static void tegra_pmc_sync_state(struct device *dev)
{
int err;

+ /*
+ * Newer device-trees have power domains, but we need to prepare all
+ * device drivers with runtime PM and OPP support first, otherwise
+ * state syncing is unsafe.
+ */
+ if (!pmc->soc->supports_core_domain)
+ return;
+
/*
* Older device-trees don't have core PD, and thus, there are
* no dependencies that will block the state syncing. We shouldn't
--
2.32.0

2021-09-26 22:44:55

by Dmitry Osipenko

[permalink] [raw]
Subject: [PATCH v13 26/35] soc/tegra: fuse: Use resource-managed helpers

Use resource-managed helpers to make code cleaner and more correct,
properly releasing all resources in case of driver probe error.

Signed-off-by: Dmitry Osipenko <[email protected]>
---
drivers/soc/tegra/fuse/fuse-tegra.c | 32 ++++++++++++++------------
drivers/soc/tegra/fuse/fuse-tegra20.c | 33 ++++++++++++++++++++++++---
2 files changed, 48 insertions(+), 17 deletions(-)

diff --git a/drivers/soc/tegra/fuse/fuse-tegra.c b/drivers/soc/tegra/fuse/fuse-tegra.c
index cc032729a143..fe4f935ce73a 100644
--- a/drivers/soc/tegra/fuse/fuse-tegra.c
+++ b/drivers/soc/tegra/fuse/fuse-tegra.c
@@ -182,6 +182,12 @@ static const struct nvmem_cell_info tegra_fuse_cells[] = {
},
};

+static void tegra_fuse_restore(void *base)
+{
+ fuse->clk = NULL;
+ fuse->base = base;
+}
+
static int tegra_fuse_probe(struct platform_device *pdev)
{
void __iomem *base = fuse->base;
@@ -189,13 +195,16 @@ static int tegra_fuse_probe(struct platform_device *pdev)
struct resource *res;
int err;

+ err = devm_add_action(&pdev->dev, tegra_fuse_restore, base);
+ if (err)
+ return err;
+
/* take over the memory region from the early initialization */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
fuse->phys = res->start;
fuse->base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(fuse->base)) {
err = PTR_ERR(fuse->base);
- fuse->base = base;
return err;
}

@@ -205,19 +214,20 @@ static int tegra_fuse_probe(struct platform_device *pdev)
dev_err(&pdev->dev, "failed to get FUSE clock: %ld",
PTR_ERR(fuse->clk));

- fuse->base = base;
return PTR_ERR(fuse->clk);
}

platform_set_drvdata(pdev, fuse);
fuse->dev = &pdev->dev;

- pm_runtime_enable(&pdev->dev);
+ err = devm_pm_runtime_enable(&pdev->dev);
+ if (err)
+ return err;

if (fuse->soc->probe) {
err = fuse->soc->probe(fuse);
if (err < 0)
- goto restore;
+ return err;
}

memset(&nvmem, 0, sizeof(nvmem));
@@ -241,7 +251,7 @@ static int tegra_fuse_probe(struct platform_device *pdev)
err = PTR_ERR(fuse->nvmem);
dev_err(&pdev->dev, "failed to register NVMEM device: %d\n",
err);
- goto restore;
+ return err;
}

fuse->rst = devm_reset_control_get_optional(&pdev->dev, "fuse");
@@ -249,7 +259,7 @@ static int tegra_fuse_probe(struct platform_device *pdev)
err = PTR_ERR(fuse->rst);
dev_err(&pdev->dev, "failed to get FUSE reset: %pe\n",
fuse->rst);
- goto restore;
+ return err;
}

/*
@@ -258,26 +268,20 @@ static int tegra_fuse_probe(struct platform_device *pdev)
*/
err = pm_runtime_resume_and_get(&pdev->dev);
if (err)
- goto restore;
+ return err;

err = reset_control_reset(fuse->rst);
pm_runtime_put(&pdev->dev);

if (err < 0) {
dev_err(&pdev->dev, "failed to reset FUSE: %d\n", err);
- goto restore;
+ return err;
}

/* release the early I/O memory mapping */
iounmap(base);

return 0;
-
-restore:
- fuse->clk = NULL;
- fuse->base = base;
- pm_runtime_disable(&pdev->dev);
- return err;
}

static int __maybe_unused tegra_fuse_runtime_resume(struct device *dev)
diff --git a/drivers/soc/tegra/fuse/fuse-tegra20.c b/drivers/soc/tegra/fuse/fuse-tegra20.c
index 8ec9fc5e5e4b..12503f563e36 100644
--- a/drivers/soc/tegra/fuse/fuse-tegra20.c
+++ b/drivers/soc/tegra/fuse/fuse-tegra20.c
@@ -94,9 +94,28 @@ static bool dma_filter(struct dma_chan *chan, void *filter_param)
return of_device_is_compatible(np, "nvidia,tegra20-apbdma");
}

+static void tegra20_fuse_release_channel(void *data)
+{
+ struct tegra_fuse *fuse = data;
+
+ dma_release_channel(fuse->apbdma.chan);
+ fuse->apbdma.chan = NULL;
+}
+
+static void tegra20_fuse_free_coherent(void *data)
+{
+ struct tegra_fuse *fuse = data;
+
+ dma_free_coherent(fuse->dev, sizeof(u32), fuse->apbdma.virt,
+ fuse->apbdma.phys);
+ fuse->apbdma.virt = NULL;
+ fuse->apbdma.phys = 0x0;
+}
+
static int tegra20_fuse_probe(struct tegra_fuse *fuse)
{
dma_cap_mask_t mask;
+ int err;

dma_cap_zero(mask);
dma_cap_set(DMA_SLAVE, mask);
@@ -105,13 +124,21 @@ static int tegra20_fuse_probe(struct tegra_fuse *fuse)
if (!fuse->apbdma.chan)
return -EPROBE_DEFER;

+ err = devm_add_action_or_reset(fuse->dev, tegra20_fuse_release_channel,
+ fuse);
+ if (err)
+ return err;
+
fuse->apbdma.virt = dma_alloc_coherent(fuse->dev, sizeof(u32),
&fuse->apbdma.phys,
GFP_KERNEL);
- if (!fuse->apbdma.virt) {
- dma_release_channel(fuse->apbdma.chan);
+ if (!fuse->apbdma.virt)
return -ENOMEM;
- }
+
+ err = devm_add_action_or_reset(fuse->dev, tegra20_fuse_free_coherent,
+ fuse);
+ if (err)
+ return err;

fuse->apbdma.config.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
fuse->apbdma.config.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
--
2.32.0

2021-09-26 22:45:00

by Dmitry Osipenko

[permalink] [raw]
Subject: [PATCH v13 05/35] dt-bindings: clock: tegra-car: Document new clock sub-nodes

Document sub-nodes which describe Tegra SoC clocks that require a higher
voltage of the core power domain in order to operate properly on a higher
clock rates. Each node contains a phandle to OPP table and power domain.

The root PLLs and system clocks don't have any specific device dedicated
to them, clock controller is in charge of managing power for them.

Reviewed-by: Rob Herring <[email protected]>
Signed-off-by: Dmitry Osipenko <[email protected]>
---
.../bindings/clock/nvidia,tegra20-car.yaml | 37 +++++++++++++++++++
1 file changed, 37 insertions(+)

diff --git a/Documentation/devicetree/bindings/clock/nvidia,tegra20-car.yaml b/Documentation/devicetree/bindings/clock/nvidia,tegra20-car.yaml
index 459d2a525393..f832abb7f11a 100644
--- a/Documentation/devicetree/bindings/clock/nvidia,tegra20-car.yaml
+++ b/Documentation/devicetree/bindings/clock/nvidia,tegra20-car.yaml
@@ -42,6 +42,36 @@ properties:
"#reset-cells":
const: 1

+patternProperties:
+ "^(sclk)|(pll-[cem])$":
+ type: object
+ properties:
+ compatible:
+ enum:
+ - nvidia,tegra20-sclk
+ - nvidia,tegra30-sclk
+ - nvidia,tegra30-pllc
+ - nvidia,tegra30-plle
+ - nvidia,tegra30-pllm
+
+ operating-points-v2: true
+
+ clocks:
+ items:
+ - description: node's clock
+
+ power-domains:
+ maxItems: 1
+ description: phandle to the core SoC power domain
+
+ required:
+ - compatible
+ - operating-points-v2
+ - clocks
+ - power-domains
+
+ additionalProperties: false
+
required:
- compatible
- reg
@@ -59,6 +89,13 @@ examples:
reg = <0x60006000 0x1000>;
#clock-cells = <1>;
#reset-cells = <1>;
+
+ sclk {
+ compatible = "nvidia,tegra20-sclk";
+ operating-points-v2 = <&opp_table>;
+ clocks = <&tegra_car TEGRA20_CLK_SCLK>;
+ power-domains = <&domain>;
+ };
};

usb-controller@c5004000 {
--
2.32.0

2021-09-26 22:45:06

by Dmitry Osipenko

[permalink] [raw]
Subject: [PATCH v13 20/35] mtd: rawnand: tegra: Add runtime PM and OPP support

The NAND on Tegra belongs to the core power domain and we're going to
enable GENPD support for the core domain. Now NAND must be resumed using
runtime PM API in order to initialize the NAND power state. Add runtime PM
and OPP support to the NAND driver.

Acked-by: Miquel Raynal <[email protected]>
Signed-off-by: Dmitry Osipenko <[email protected]>
---
drivers/mtd/nand/raw/tegra_nand.c | 55 ++++++++++++++++++++++++++-----
1 file changed, 47 insertions(+), 8 deletions(-)

diff --git a/drivers/mtd/nand/raw/tegra_nand.c b/drivers/mtd/nand/raw/tegra_nand.c
index 32431bbe69b8..098fcc9cb9df 100644
--- a/drivers/mtd/nand/raw/tegra_nand.c
+++ b/drivers/mtd/nand/raw/tegra_nand.c
@@ -17,8 +17,11 @@
#include <linux/mtd/rawnand.h>
#include <linux/of.h>
#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
#include <linux/reset.h>

+#include <soc/tegra/common.h>
+
#define COMMAND 0x00
#define COMMAND_GO BIT(31)
#define COMMAND_CLE BIT(30)
@@ -1151,6 +1154,7 @@ static int tegra_nand_probe(struct platform_device *pdev)
return -ENOMEM;

ctrl->dev = &pdev->dev;
+ platform_set_drvdata(pdev, ctrl);
nand_controller_init(&ctrl->controller);
ctrl->controller.ops = &tegra_nand_controller_ops;

@@ -1166,14 +1170,22 @@ static int tegra_nand_probe(struct platform_device *pdev)
if (IS_ERR(ctrl->clk))
return PTR_ERR(ctrl->clk);

- err = clk_prepare_enable(ctrl->clk);
+ err = devm_pm_runtime_enable(&pdev->dev);
+ if (err)
+ return err;
+
+ err = devm_tegra_core_dev_init_opp_table_common(&pdev->dev);
+ if (err)
+ return err;
+
+ err = pm_runtime_resume_and_get(&pdev->dev);
if (err)
return err;

err = reset_control_reset(rst);
if (err) {
dev_err(ctrl->dev, "Failed to reset HW: %d\n", err);
- goto err_disable_clk;
+ goto err_put_pm;
}

writel_relaxed(HWSTATUS_CMD_DEFAULT, ctrl->regs + HWSTATUS_CMD);
@@ -1188,21 +1200,19 @@ static int tegra_nand_probe(struct platform_device *pdev)
dev_name(&pdev->dev), ctrl);
if (err) {
dev_err(ctrl->dev, "Failed to get IRQ: %d\n", err);
- goto err_disable_clk;
+ goto err_put_pm;
}

writel_relaxed(DMA_MST_CTRL_IS_DONE, ctrl->regs + DMA_MST_CTRL);

err = tegra_nand_chips_init(ctrl->dev, ctrl);
if (err)
- goto err_disable_clk;
-
- platform_set_drvdata(pdev, ctrl);
+ goto err_put_pm;

return 0;

-err_disable_clk:
- clk_disable_unprepare(ctrl->clk);
+err_put_pm:
+ pm_runtime_put(ctrl->dev);
return err;
}

@@ -1219,11 +1229,39 @@ static int tegra_nand_remove(struct platform_device *pdev)

nand_cleanup(chip);

+ pm_runtime_put(ctrl->dev);
+
+ return 0;
+}
+
+static int __maybe_unused tegra_nand_runtime_resume(struct device *dev)
+{
+ struct tegra_nand_controller *ctrl = dev_get_drvdata(dev);
+ int err;
+
+ err = clk_prepare_enable(ctrl->clk);
+ if (err) {
+ dev_err(dev, "Failed to enable clock: %d\n", err);
+ return err;
+ }
+
+ return 0;
+}
+
+static int __maybe_unused tegra_nand_runtime_suspend(struct device *dev)
+{
+ struct tegra_nand_controller *ctrl = dev_get_drvdata(dev);
+
clk_disable_unprepare(ctrl->clk);

return 0;
}

+static const struct dev_pm_ops tegra_nand_pm = {
+ SET_RUNTIME_PM_OPS(tegra_nand_runtime_suspend, tegra_nand_runtime_resume,
+ NULL)
+};
+
static const struct of_device_id tegra_nand_of_match[] = {
{ .compatible = "nvidia,tegra20-nand" },
{ /* sentinel */ }
@@ -1234,6 +1272,7 @@ static struct platform_driver tegra_nand_driver = {
.driver = {
.name = "tegra-nand",
.of_match_table = tegra_nand_of_match,
+ .pm = &tegra_nand_pm,
},
.probe = tegra_nand_probe,
.remove = tegra_nand_remove,
--
2.32.0

2021-09-26 22:45:08

by Dmitry Osipenko

[permalink] [raw]
Subject: [PATCH v13 18/35] pwm: tegra: Add runtime PM and OPP support

The PWM on Tegra belongs to the core power domain and we're going to
enable GENPD support for the core domain. Now PWM must be resumed using
runtime PM API in order to initialize the PWM power state. The PWM clock
rate must be changed using OPP API that will reconfigure the power domain
performance state in accordance to the rate. Add runtime PM and OPP
support to the PWM driver.

Signed-off-by: Dmitry Osipenko <[email protected]>
---
drivers/pwm/pwm-tegra.c | 88 ++++++++++++++++++++++++++++++++---------
1 file changed, 70 insertions(+), 18 deletions(-)

diff --git a/drivers/pwm/pwm-tegra.c b/drivers/pwm/pwm-tegra.c
index 11a10b575ace..afb5ecde007e 100644
--- a/drivers/pwm/pwm-tegra.c
+++ b/drivers/pwm/pwm-tegra.c
@@ -42,12 +42,16 @@
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_device.h>
+#include <linux/pm_opp.h>
#include <linux/pwm.h>
#include <linux/platform_device.h>
#include <linux/pinctrl/consumer.h>
+#include <linux/pm_runtime.h>
#include <linux/slab.h>
#include <linux/reset.h>

+#include <soc/tegra/common.h>
+
#define PWM_ENABLE (1 << 31)
#define PWM_DUTY_WIDTH 8
#define PWM_DUTY_SHIFT 16
@@ -145,7 +149,7 @@ static int tegra_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
required_clk_rate =
(NSEC_PER_SEC / period_ns) << PWM_DUTY_WIDTH;

- err = clk_set_rate(pc->clk, required_clk_rate);
+ err = dev_pm_opp_set_rate(pc->dev, required_clk_rate);
if (err < 0)
return -EINVAL;

@@ -181,8 +185,8 @@ static int tegra_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
* before writing the register. Otherwise, keep it enabled.
*/
if (!pwm_is_enabled(pwm)) {
- err = clk_prepare_enable(pc->clk);
- if (err < 0)
+ err = pm_runtime_resume_and_get(pc->dev);
+ if (err)
return err;
} else
val |= PWM_ENABLE;
@@ -193,7 +197,7 @@ static int tegra_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
* If the PWM is not enabled, turn the clock off again to save power.
*/
if (!pwm_is_enabled(pwm))
- clk_disable_unprepare(pc->clk);
+ pm_runtime_put(pc->dev);

return 0;
}
@@ -204,8 +208,8 @@ static int tegra_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
int rc = 0;
u32 val;

- rc = clk_prepare_enable(pc->clk);
- if (rc < 0)
+ rc = pm_runtime_resume_and_get(pc->dev);
+ if (rc)
return rc;

val = pwm_readl(pc, pwm->hwpwm);
@@ -224,7 +228,7 @@ static void tegra_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
val &= ~PWM_ENABLE;
pwm_writel(pc, pwm->hwpwm, val);

- clk_disable_unprepare(pc->clk);
+ pm_runtime_put_sync(pc->dev);
}

static const struct pwm_ops tegra_pwm_ops = {
@@ -256,11 +260,23 @@ static int tegra_pwm_probe(struct platform_device *pdev)
if (IS_ERR(pwm->clk))
return PTR_ERR(pwm->clk);

+ ret = devm_pm_runtime_enable(&pdev->dev);
+ if (ret)
+ return ret;
+
+ ret = devm_tegra_core_dev_init_opp_table_common(&pdev->dev);
+ if (ret)
+ return ret;
+
+ ret = pm_runtime_resume_and_get(&pdev->dev);
+ if (ret)
+ return ret;
+
/* Set maximum frequency of the IP */
- ret = clk_set_rate(pwm->clk, pwm->soc->max_frequency);
+ ret = dev_pm_opp_set_rate(pwm->dev, pwm->soc->max_frequency);
if (ret < 0) {
dev_err(&pdev->dev, "Failed to set max frequency: %d\n", ret);
- return ret;
+ goto put_pm;
}

/*
@@ -278,7 +294,7 @@ static int tegra_pwm_probe(struct platform_device *pdev)
if (IS_ERR(pwm->rst)) {
ret = PTR_ERR(pwm->rst);
dev_err(&pdev->dev, "Reset control is not found: %d\n", ret);
- return ret;
+ goto put_pm;
}

reset_control_deassert(pwm->rst);
@@ -291,34 +307,67 @@ static int tegra_pwm_probe(struct platform_device *pdev)
if (ret < 0) {
dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret);
reset_control_assert(pwm->rst);
- return ret;
+ goto put_pm;
}

+ pm_runtime_put(&pdev->dev);
+
return 0;
+put_pm:
+ pm_runtime_put(&pdev->dev);
+ return ret;
}

static int tegra_pwm_remove(struct platform_device *pdev)
{
struct tegra_pwm_chip *pc = platform_get_drvdata(pdev);
+ int err;

pwmchip_remove(&pc->chip);

+ err = pm_runtime_resume_and_get(pc->dev);
+ if (err)
+ return err;
+
reset_control_assert(pc->rst);
+ pm_runtime_put(pc->dev);

return 0;
}

-#ifdef CONFIG_PM_SLEEP
-static int tegra_pwm_suspend(struct device *dev)
+static int __maybe_unused tegra_pwm_runtime_suspend(struct device *dev)
{
- return pinctrl_pm_select_sleep_state(dev);
+ struct tegra_pwm_chip *pc = dev_get_drvdata(dev);
+ int err;
+
+ clk_disable_unprepare(pc->clk);
+
+ err = pinctrl_pm_select_sleep_state(dev);
+ if (err) {
+ clk_prepare_enable(pc->clk);
+ return err;
+ }
+
+ return 0;
}

-static int tegra_pwm_resume(struct device *dev)
+static int __maybe_unused tegra_pwm_runtime_resume(struct device *dev)
{
- return pinctrl_pm_select_default_state(dev);
+ struct tegra_pwm_chip *pc = dev_get_drvdata(dev);
+ int err;
+
+ err = pinctrl_pm_select_default_state(dev);
+ if (err)
+ return err;
+
+ err = clk_prepare_enable(pc->clk);
+ if (err) {
+ pinctrl_pm_select_sleep_state(dev);
+ return err;
+ }
+
+ return 0;
}
-#endif

static const struct tegra_pwm_soc tegra20_pwm_soc = {
.num_channels = 4,
@@ -344,7 +393,10 @@ static const struct of_device_id tegra_pwm_of_match[] = {
MODULE_DEVICE_TABLE(of, tegra_pwm_of_match);

static const struct dev_pm_ops tegra_pwm_pm_ops = {
- SET_SYSTEM_SLEEP_PM_OPS(tegra_pwm_suspend, tegra_pwm_resume)
+ SET_RUNTIME_PM_OPS(tegra_pwm_runtime_suspend, tegra_pwm_runtime_resume,
+ NULL)
+ SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
+ pm_runtime_force_resume)
};

static struct platform_driver tegra_pwm_driver = {
--
2.32.0

2021-09-26 22:45:20

by Dmitry Osipenko

[permalink] [raw]
Subject: [PATCH v13 17/35] bus: tegra-gmi: Add runtime PM and OPP support

The GMI bus on Tegra belongs to the core power domain and we're going to
enable GENPD support for the core domain. Now GMI must be resumed using
runtime PM API in order to initialize the GMI power state. Add runtime PM
and OPP support to the GMI driver.

Signed-off-by: Dmitry Osipenko <[email protected]>
---
drivers/bus/tegra-gmi.c | 52 ++++++++++++++++++++++++++++++++++++-----
1 file changed, 46 insertions(+), 6 deletions(-)

diff --git a/drivers/bus/tegra-gmi.c b/drivers/bus/tegra-gmi.c
index a6570789f7af..72ef8a8c236b 100644
--- a/drivers/bus/tegra-gmi.c
+++ b/drivers/bus/tegra-gmi.c
@@ -13,8 +13,11 @@
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of_device.h>
+#include <linux/pm_runtime.h>
#include <linux/reset.h>

+#include <soc/tegra/common.h>
+
#define TEGRA_GMI_CONFIG 0x00
#define TEGRA_GMI_CONFIG_GO BIT(31)
#define TEGRA_GMI_BUS_WIDTH_32BIT BIT(30)
@@ -54,9 +57,9 @@ static int tegra_gmi_enable(struct tegra_gmi *gmi)
{
int err;

- err = clk_prepare_enable(gmi->clk);
- if (err < 0) {
- dev_err(gmi->dev, "failed to enable clock: %d\n", err);
+ err = pm_runtime_resume_and_get(gmi->dev);
+ if (err) {
+ pm_runtime_disable(gmi->dev);
return err;
}

@@ -83,7 +86,8 @@ static void tegra_gmi_disable(struct tegra_gmi *gmi)
writel(config, gmi->base + TEGRA_GMI_CONFIG);

reset_control_assert(gmi->rst);
- clk_disable_unprepare(gmi->clk);
+
+ pm_runtime_put(gmi->dev);
}

static int tegra_gmi_parse_dt(struct tegra_gmi *gmi)
@@ -213,6 +217,7 @@ static int tegra_gmi_probe(struct platform_device *pdev)
if (!gmi)
return -ENOMEM;

+ platform_set_drvdata(pdev, gmi);
gmi->dev = dev;

res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
@@ -232,6 +237,14 @@ static int tegra_gmi_probe(struct platform_device *pdev)
return PTR_ERR(gmi->rst);
}

+ err = devm_pm_runtime_enable(gmi->dev);
+ if (err)
+ return err;
+
+ err = devm_tegra_core_dev_init_opp_table_common(&pdev->dev);
+ if (err)
+ return err;
+
err = tegra_gmi_parse_dt(gmi);
if (err)
return err;
@@ -247,8 +260,6 @@ static int tegra_gmi_probe(struct platform_device *pdev)
return err;
}

- platform_set_drvdata(pdev, gmi);
-
return 0;
}

@@ -262,6 +273,34 @@ static int tegra_gmi_remove(struct platform_device *pdev)
return 0;
}

+static int __maybe_unused tegra_gmi_runtime_resume(struct device *dev)
+{
+ struct tegra_gmi *gmi = dev_get_drvdata(dev);
+ int err;
+
+ err = clk_prepare_enable(gmi->clk);
+ if (err < 0) {
+ dev_err(gmi->dev, "failed to enable clock: %d\n", err);
+ return err;
+ }
+
+ return 0;
+}
+
+static int __maybe_unused tegra_gmi_runtime_suspend(struct device *dev)
+{
+ struct tegra_gmi *gmi = dev_get_drvdata(dev);
+
+ clk_disable_unprepare(gmi->clk);
+
+ return 0;
+}
+
+static const struct dev_pm_ops tegra_gmi_pm = {
+ SET_RUNTIME_PM_OPS(tegra_gmi_runtime_suspend, tegra_gmi_runtime_resume,
+ NULL)
+};
+
static const struct of_device_id tegra_gmi_id_table[] = {
{ .compatible = "nvidia,tegra20-gmi", },
{ .compatible = "nvidia,tegra30-gmi", },
@@ -275,6 +314,7 @@ static struct platform_driver tegra_gmi_driver = {
.driver = {
.name = "tegra-gmi",
.of_match_table = tegra_gmi_id_table,
+ .pm = &tegra_gmi_pm,
},
};
module_platform_driver(tegra_gmi_driver);
--
2.32.0

2021-09-26 22:45:24

by Dmitry Osipenko

[permalink] [raw]
Subject: [PATCH v13 13/35] drm/tegra: gr2d: Support generic power domain and runtime PM

Add runtime power management and support generic power domains.

Tested-by: Peter Geis <[email protected]> # Ouya T30
Tested-by: Paul Fertser <[email protected]> # PAZ00 T20
Tested-by: Nicolas Chauvet <[email protected]> # PAZ00 T20 and TK1 T124
Tested-by: Matt Merhar <[email protected]> # Ouya T30
Signed-off-by: Dmitry Osipenko <[email protected]>
---
drivers/gpu/drm/tegra/gr2d.c | 155 +++++++++++++++++++++++++++++++++--
1 file changed, 147 insertions(+), 8 deletions(-)

diff --git a/drivers/gpu/drm/tegra/gr2d.c b/drivers/gpu/drm/tegra/gr2d.c
index de288cba3905..13df8f118f75 100644
--- a/drivers/gpu/drm/tegra/gr2d.c
+++ b/drivers/gpu/drm/tegra/gr2d.c
@@ -7,11 +7,21 @@
#include <linux/iommu.h>
#include <linux/module.h>
#include <linux/of_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/reset.h>
+
+#include <soc/tegra/common.h>

#include "drm.h"
#include "gem.h"
#include "gr2d.h"

+enum {
+ RST_MC,
+ RST_GR2D,
+ RST_GR2D_MAX,
+};
+
struct gr2d_soc {
unsigned int version;
};
@@ -21,6 +31,9 @@ struct gr2d {
struct host1x_channel *channel;
struct clk *clk;

+ struct reset_control_bulk_data resets[RST_GR2D_MAX];
+ unsigned int nresets;
+
const struct gr2d_soc *soc;

DECLARE_BITMAP(addr_regs, GR2D_NUM_REGS);
@@ -101,16 +114,24 @@ static int gr2d_open_channel(struct tegra_drm_client *client,
struct tegra_drm_context *context)
{
struct gr2d *gr2d = to_gr2d(client);
+ int err;

context->channel = host1x_channel_get(gr2d->channel);
if (!context->channel)
return -ENOMEM;

+ err = pm_runtime_resume_and_get(client->base.dev);
+ if (err) {
+ host1x_channel_put(context->channel);
+ return err;
+ }
+
return 0;
}

static void gr2d_close_channel(struct tegra_drm_context *context)
{
+ pm_runtime_put_sync(context->client->base.dev);
host1x_channel_put(context->channel);
}

@@ -190,6 +211,27 @@ static const u32 gr2d_addr_regs[] = {
GR2D_VA_BASE_ADDR_SB,
};

+static int gr2d_get_resets(struct device *dev, struct gr2d *gr2d)
+{
+ int err;
+
+ gr2d->resets[RST_MC].id = "mc";
+ gr2d->resets[RST_GR2D].id = "2d";
+ gr2d->nresets = RST_GR2D_MAX;
+
+ err = devm_reset_control_bulk_get_optional_exclusive_released(
+ dev, gr2d->nresets, gr2d->resets);
+ if (err) {
+ dev_err(dev, "failed to get reset: %d\n", err);
+ return err;
+ }
+
+ if (WARN_ON(!gr2d->resets[RST_GR2D].rstc))
+ return -ENOENT;
+
+ return 0;
+}
+
static int gr2d_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
@@ -202,6 +244,8 @@ static int gr2d_probe(struct platform_device *pdev)
if (!gr2d)
return -ENOMEM;

+ platform_set_drvdata(pdev, gr2d);
+
gr2d->soc = of_device_get_match_data(dev);

syncpts = devm_kzalloc(dev, sizeof(*syncpts), GFP_KERNEL);
@@ -214,11 +258,9 @@ static int gr2d_probe(struct platform_device *pdev)
return PTR_ERR(gr2d->clk);
}

- err = clk_prepare_enable(gr2d->clk);
- if (err) {
- dev_err(dev, "cannot turn on clock\n");
+ err = gr2d_get_resets(dev, gr2d);
+ if (err)
return err;
- }

INIT_LIST_HEAD(&gr2d->client.base.list);
gr2d->client.base.ops = &gr2d_client_ops;
@@ -231,20 +273,31 @@ static int gr2d_probe(struct platform_device *pdev)
gr2d->client.version = gr2d->soc->version;
gr2d->client.ops = &gr2d_ops;

+ pm_runtime_enable(dev);
+ pm_runtime_use_autosuspend(dev);
+ pm_runtime_set_autosuspend_delay(dev, 200);
+
+ err = devm_tegra_core_dev_init_opp_table_common(dev);
+ if (err)
+ goto disable_rpm;
+
err = host1x_client_register(&gr2d->client.base);
if (err < 0) {
dev_err(dev, "failed to register host1x client: %d\n", err);
- clk_disable_unprepare(gr2d->clk);
- return err;
+ goto disable_rpm;
}

/* initialize address register map */
for (i = 0; i < ARRAY_SIZE(gr2d_addr_regs); i++)
set_bit(gr2d_addr_regs[i], gr2d->addr_regs);

- platform_set_drvdata(pdev, gr2d);
-
return 0;
+
+disable_rpm:
+ pm_runtime_dont_use_autosuspend(dev);
+ pm_runtime_disable(dev);
+
+ return err;
}

static int gr2d_remove(struct platform_device *pdev)
@@ -259,15 +312,101 @@ static int gr2d_remove(struct platform_device *pdev)
return err;
}

+ pm_runtime_dont_use_autosuspend(&pdev->dev);
+ pm_runtime_disable(&pdev->dev);
+
+ return 0;
+}
+
+static int __maybe_unused gr2d_runtime_suspend(struct device *dev)
+{
+ struct gr2d *gr2d = dev_get_drvdata(dev);
+ int err;
+
+ host1x_channel_stop(gr2d->channel);
+ reset_control_bulk_release(gr2d->nresets, gr2d->resets);
+
+ /*
+ * GR2D module shouldn't be reset while hardware is idling, otherwise
+ * host1x's cmdproc will stuck on trying to access any G2 register
+ * after reset. GR2D module could be either hot-reset or reset after
+ * power-gating of the HEG partition. Hence we will put in reset only
+ * the memory client part of the module, the HEG GENPD will take care
+ * of resetting GR2D module across power-gating.
+ *
+ * On Tegra20 there is no HEG partition, but it's okay to have
+ * undetermined h/w state since userspace is expected to reprogram
+ * the state on each job submission anyways.
+ */
+ err = reset_control_acquire(gr2d->resets[RST_MC].rstc);
+ if (err) {
+ dev_err(dev, "failed to acquire MC reset: %d\n", err);
+ goto acquire_reset;
+ }
+
+ err = reset_control_assert(gr2d->resets[RST_MC].rstc);
+ reset_control_release(gr2d->resets[RST_MC].rstc);
+ if (err) {
+ dev_err(dev, "failed to assert MC reset: %d\n", err);
+ goto acquire_reset;
+ }
+
clk_disable_unprepare(gr2d->clk);

return 0;
+
+acquire_reset:
+ reset_control_bulk_acquire(gr2d->nresets, gr2d->resets);
+ reset_control_bulk_deassert(gr2d->nresets, gr2d->resets);
+
+ return err;
}

+static int __maybe_unused gr2d_runtime_resume(struct device *dev)
+{
+ struct gr2d *gr2d = dev_get_drvdata(dev);
+ int err;
+
+ err = reset_control_bulk_acquire(gr2d->nresets, gr2d->resets);
+ if (err) {
+ dev_err(dev, "failed to acquire reset: %d\n", err);
+ return err;
+ }
+
+ err = clk_prepare_enable(gr2d->clk);
+ if (err) {
+ dev_err(dev, "failed to enable clock: %d\n", err);
+ goto release_reset;
+ }
+
+ /* this is a reset array which deasserts both 2D MC and 2D itself */
+ err = reset_control_bulk_deassert(gr2d->nresets, gr2d->resets);
+ if (err) {
+ dev_err(dev, "failed to deassert reset: %d\n", err);
+ goto disable_clk;
+ }
+
+ return 0;
+
+disable_clk:
+ clk_disable_unprepare(gr2d->clk);
+release_reset:
+ reset_control_bulk_release(gr2d->nresets, gr2d->resets);
+
+ return err;
+}
+
+static const struct dev_pm_ops tegra_gr2d_pm = {
+ SET_RUNTIME_PM_OPS(gr2d_runtime_suspend, gr2d_runtime_resume, NULL)
+ SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
+ pm_runtime_force_resume)
+};
+
struct platform_driver tegra_gr2d_driver = {
.driver = {
.name = "tegra-gr2d",
.of_match_table = gr2d_match,
+ .pm = &tegra_gr2d_pm,
},
.probe = gr2d_probe,
.remove = gr2d_remove,
--
2.32.0

2021-09-26 22:45:27

by Dmitry Osipenko

[permalink] [raw]
Subject: [PATCH v13 19/35] mmc: sdhci-tegra: Add runtime PM and OPP support

The SDHCI on Tegra belongs to the core power domain and we're going to
enable GENPD support for the core domain. Now SDHCI must be resumed using
runtime PM API in order to initialize the SDHCI power state. The SDHCI
clock rate must be changed using OPP API that will reconfigure the power
domain performance state in accordance to the rate. Add runtime PM and OPP
support to the SDHCI driver.

Signed-off-by: Dmitry Osipenko <[email protected]>
---
drivers/mmc/host/sdhci-tegra.c | 82 +++++++++++++++++++++++++++-------
1 file changed, 66 insertions(+), 16 deletions(-)

diff --git a/drivers/mmc/host/sdhci-tegra.c b/drivers/mmc/host/sdhci-tegra.c
index a5001875876b..6cf3712bf0a6 100644
--- a/drivers/mmc/host/sdhci-tegra.c
+++ b/drivers/mmc/host/sdhci-tegra.c
@@ -15,6 +15,8 @@
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/pinctrl/consumer.h>
+#include <linux/pm_opp.h>
+#include <linux/pm_runtime.h>
#include <linux/regulator/consumer.h>
#include <linux/reset.h>
#include <linux/mmc/card.h>
@@ -24,6 +26,8 @@
#include <linux/gpio/consumer.h>
#include <linux/ktime.h>

+#include <soc/tegra/common.h>
+
#include "sdhci-pltfm.h"
#include "cqhci.h"

@@ -760,7 +764,9 @@ static void tegra_sdhci_set_clock(struct sdhci_host *host, unsigned int clock)
{
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
struct sdhci_tegra *tegra_host = sdhci_pltfm_priv(pltfm_host);
+ struct device *dev = mmc_dev(host->mmc);
unsigned long host_clk;
+ int err;

if (!clock)
return sdhci_set_clock(host, clock);
@@ -778,7 +784,12 @@ static void tegra_sdhci_set_clock(struct sdhci_host *host, unsigned int clock)
* from clk_get_rate() is used.
*/
host_clk = tegra_host->ddr_signaling ? clock * 2 : clock;
- clk_set_rate(pltfm_host->clk, host_clk);
+
+ err = dev_pm_opp_set_rate(dev, host_clk);
+ if (err)
+ dev_err(dev, "failed to set clk rate to %luHz: %d\n",
+ host_clk, err);
+
tegra_host->curr_clk_rate = host_clk;
if (tegra_host->ddr_signaling)
host->max_clk = host_clk;
@@ -1705,7 +1716,6 @@ static int sdhci_tegra_probe(struct platform_device *pdev)
"failed to get clock\n");
goto err_clk_get;
}
- clk_prepare_enable(clk);
pltfm_host->clk = clk;

tegra_host->rst = devm_reset_control_get_exclusive(&pdev->dev,
@@ -1716,15 +1726,25 @@ static int sdhci_tegra_probe(struct platform_device *pdev)
goto err_rst_get;
}

+ pm_runtime_enable(&pdev->dev);
+
+ rc = devm_tegra_core_dev_init_opp_table_common(&pdev->dev);
+ if (rc)
+ goto err_pm_get;
+
+ rc = pm_runtime_resume_and_get(&pdev->dev);
+ if (rc)
+ goto err_pm_get;
+
rc = reset_control_assert(tegra_host->rst);
if (rc)
- goto err_rst_get;
+ goto err_rst_assert;

usleep_range(2000, 4000);

rc = reset_control_deassert(tegra_host->rst);
if (rc)
- goto err_rst_get;
+ goto err_rst_assert;

usleep_range(2000, 4000);

@@ -1736,8 +1756,11 @@ static int sdhci_tegra_probe(struct platform_device *pdev)

err_add_host:
reset_control_assert(tegra_host->rst);
+err_rst_assert:
+ pm_runtime_put(&pdev->dev);
+err_pm_get:
+ pm_runtime_disable(&pdev->dev);
err_rst_get:
- clk_disable_unprepare(pltfm_host->clk);
err_clk_get:
clk_disable_unprepare(tegra_host->tmclk);
err_power_req:
@@ -1756,19 +1779,38 @@ static int sdhci_tegra_remove(struct platform_device *pdev)

reset_control_assert(tegra_host->rst);
usleep_range(2000, 4000);
- clk_disable_unprepare(pltfm_host->clk);
- clk_disable_unprepare(tegra_host->tmclk);

+ pm_runtime_put(&pdev->dev);
+ pm_runtime_disable(&pdev->dev);
+
+ clk_disable_unprepare(tegra_host->tmclk);
sdhci_pltfm_free(pdev);

return 0;
}

-#ifdef CONFIG_PM_SLEEP
-static int __maybe_unused sdhci_tegra_suspend(struct device *dev)
+static int __maybe_unused sdhci_tegra_runtime_suspend(struct device *dev)
{
struct sdhci_host *host = dev_get_drvdata(dev);
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+
+ clk_disable_unprepare(pltfm_host->clk);
+
+ return 0;
+}
+
+static int __maybe_unused sdhci_tegra_runtime_resume(struct device *dev)
+{
+ struct sdhci_host *host = dev_get_drvdata(dev);
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+
+ return clk_prepare_enable(pltfm_host->clk);
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int sdhci_tegra_suspend(struct device *dev)
+{
+ struct sdhci_host *host = dev_get_drvdata(dev);
int ret;

if (host->mmc->caps2 & MMC_CAP2_CQE) {
@@ -1783,17 +1825,22 @@ static int __maybe_unused sdhci_tegra_suspend(struct device *dev)
return ret;
}

- clk_disable_unprepare(pltfm_host->clk);
+ ret = pm_runtime_force_suspend(dev);
+ if (ret) {
+ sdhci_resume_host(host);
+ cqhci_resume(host->mmc);
+ return ret;
+ }
+
return 0;
}

-static int __maybe_unused sdhci_tegra_resume(struct device *dev)
+static int sdhci_tegra_resume(struct device *dev)
{
struct sdhci_host *host = dev_get_drvdata(dev);
- struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
int ret;

- ret = clk_prepare_enable(pltfm_host->clk);
+ ret = pm_runtime_force_resume(dev);
if (ret)
return ret;

@@ -1812,13 +1859,16 @@ static int __maybe_unused sdhci_tegra_resume(struct device *dev)
suspend_host:
sdhci_suspend_host(host);
disable_clk:
- clk_disable_unprepare(pltfm_host->clk);
+ pm_runtime_force_suspend(dev);
return ret;
}
#endif

-static SIMPLE_DEV_PM_OPS(sdhci_tegra_dev_pm_ops, sdhci_tegra_suspend,
- sdhci_tegra_resume);
+static const struct dev_pm_ops sdhci_tegra_dev_pm_ops = {
+ SET_RUNTIME_PM_OPS(sdhci_tegra_runtime_suspend, sdhci_tegra_runtime_resume,
+ NULL)
+ SET_SYSTEM_SLEEP_PM_OPS(sdhci_tegra_suspend, sdhci_tegra_resume)
+};

static struct platform_driver sdhci_tegra_driver = {
.driver = {
--
2.32.0

2021-09-26 22:45:27

by Dmitry Osipenko

[permalink] [raw]
Subject: [PATCH v13 15/35] drm/tegra: vic: Support system suspend

Hardware must be stopped before system is suspended. Add suspend-resume
callbacks.

Signed-off-by: Dmitry Osipenko <[email protected]>
---
drivers/gpu/drm/tegra/vic.c | 4 ++++
1 file changed, 4 insertions(+)

diff --git a/drivers/gpu/drm/tegra/vic.c b/drivers/gpu/drm/tegra/vic.c
index c02010ff2b7f..359dd77f8b85 100644
--- a/drivers/gpu/drm/tegra/vic.c
+++ b/drivers/gpu/drm/tegra/vic.c
@@ -315,6 +315,8 @@ static int vic_runtime_suspend(struct device *dev)
struct vic *vic = dev_get_drvdata(dev);
int err;

+ host1x_channel_stop(vic->channel);
+
err = reset_control_assert(vic->rst);
if (err < 0)
return err;
@@ -517,6 +519,8 @@ static int vic_remove(struct platform_device *pdev)

static const struct dev_pm_ops vic_pm_ops = {
SET_RUNTIME_PM_OPS(vic_runtime_suspend, vic_runtime_resume, NULL)
+ SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
+ pm_runtime_force_resume)
};

struct platform_driver tegra_vic_driver = {
--
2.32.0

2021-09-26 22:45:45

by Dmitry Osipenko

[permalink] [raw]
Subject: [PATCH v13 29/35] soc/tegra: pmc: Rename core power domain

CORE power domain uses name of device-tree node, which is inconsistent with
the names of PMC domains. Set the name to "core" to make it consistent.

Signed-off-by: Dmitry Osipenko <[email protected]>
---
drivers/soc/tegra/pmc.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/soc/tegra/pmc.c b/drivers/soc/tegra/pmc.c
index 6759f19b9cd6..3e52ceada60c 100644
--- a/drivers/soc/tegra/pmc.c
+++ b/drivers/soc/tegra/pmc.c
@@ -1353,7 +1353,7 @@ static int tegra_pmc_core_pd_add(struct tegra_pmc *pmc, struct device_node *np)
if (!genpd)
return -ENOMEM;

- genpd->name = np->name;
+ genpd->name = "core";
genpd->set_performance_state = tegra_pmc_core_pd_set_performance_state;
genpd->opp_to_performance_state = tegra_pmc_core_pd_opp_to_performance_state;

--
2.32.0

2021-09-26 22:45:47

by Dmitry Osipenko

[permalink] [raw]
Subject: [PATCH v13 30/35] soc/tegra: pmc: Enable core domain support for Tegra20 and Tegra30

All device drivers got runtime PM and OPP support. Flip the core domain
support status for Tegra20 and Tegra30 SoCs.

Signed-off-by: Dmitry Osipenko <[email protected]>
---
drivers/soc/tegra/pmc.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/drivers/soc/tegra/pmc.c b/drivers/soc/tegra/pmc.c
index 3e52ceada60c..7c7aff8120d4 100644
--- a/drivers/soc/tegra/pmc.c
+++ b/drivers/soc/tegra/pmc.c
@@ -3042,7 +3042,7 @@ static void tegra20_pmc_setup_irq_polarity(struct tegra_pmc *pmc,
}

static const struct tegra_pmc_soc tegra20_pmc_soc = {
- .supports_core_domain = false,
+ .supports_core_domain = true,
.num_powergates = ARRAY_SIZE(tegra20_powergates),
.powergates = tegra20_powergates,
.num_cpu_powergates = 0,
@@ -3103,7 +3103,7 @@ static const char * const tegra30_reset_sources[] = {
};

static const struct tegra_pmc_soc tegra30_pmc_soc = {
- .supports_core_domain = false,
+ .supports_core_domain = true,
.num_powergates = ARRAY_SIZE(tegra30_powergates),
.powergates = tegra30_powergates,
.num_cpu_powergates = ARRAY_SIZE(tegra30_cpu_powergates),
--
2.32.0

2021-09-26 22:45:51

by Dmitry Osipenko

[permalink] [raw]
Subject: [PATCH v13 14/35] drm/tegra: gr3d: Support generic power domain and runtime PM

Add runtime power management and support generic power domains.

Tested-by: Peter Geis <[email protected]> # Ouya T30
Tested-by: Paul Fertser <[email protected]> # PAZ00 T20
Tested-by: Nicolas Chauvet <[email protected]> # PAZ00 T20 and TK1 T124
Tested-by: Matt Merhar <[email protected]> # Ouya T30
Signed-off-by: Dmitry Osipenko <[email protected]>
---
drivers/gpu/drm/tegra/gr3d.c | 388 ++++++++++++++++++++++++++++++-----
1 file changed, 332 insertions(+), 56 deletions(-)

diff --git a/drivers/gpu/drm/tegra/gr3d.c b/drivers/gpu/drm/tegra/gr3d.c
index 24442ade0da3..5879402ae55e 100644
--- a/drivers/gpu/drm/tegra/gr3d.c
+++ b/drivers/gpu/drm/tegra/gr3d.c
@@ -5,32 +5,47 @@
*/

#include <linux/clk.h>
+#include <linux/delay.h>
#include <linux/host1x.h>
#include <linux/iommu.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
+#include <linux/pm_domain.h>
+#include <linux/pm_opp.h>
+#include <linux/pm_runtime.h>
#include <linux/reset.h>

+#include <soc/tegra/common.h>
#include <soc/tegra/pmc.h>

#include "drm.h"
#include "gem.h"
#include "gr3d.h"

+enum {
+ RST_MC,
+ RST_GR3D,
+ RST_MC2,
+ RST_GR3D2,
+ RST_GR3D_MAX,
+};
+
struct gr3d_soc {
unsigned int version;
+ unsigned int num_clocks;
+ unsigned int num_resets;
};

struct gr3d {
struct tegra_drm_client client;
struct host1x_channel *channel;
- struct clk *clk_secondary;
- struct clk *clk;
- struct reset_control *rst_secondary;
- struct reset_control *rst;

const struct gr3d_soc *soc;
+ struct clk_bulk_data *clocks;
+ unsigned int nclocks;
+ struct reset_control_bulk_data resets[RST_GR3D_MAX];
+ unsigned int nresets;

DECLARE_BITMAP(addr_regs, GR3D_NUM_REGS);
};
@@ -109,16 +124,24 @@ static int gr3d_open_channel(struct tegra_drm_client *client,
struct tegra_drm_context *context)
{
struct gr3d *gr3d = to_gr3d(client);
+ int err;

context->channel = host1x_channel_get(gr3d->channel);
if (!context->channel)
return -ENOMEM;

+ err = pm_runtime_resume_and_get(client->base.dev);
+ if (err) {
+ host1x_channel_put(context->channel);
+ return err;
+ }
+
return 0;
}

static void gr3d_close_channel(struct tegra_drm_context *context)
{
+ pm_runtime_put_sync(context->client->base.dev);
host1x_channel_put(context->channel);
}

@@ -155,14 +178,20 @@ static const struct tegra_drm_client_ops gr3d_ops = {

static const struct gr3d_soc tegra20_gr3d_soc = {
.version = 0x20,
+ .num_clocks = 1,
+ .num_resets = 2,
};

static const struct gr3d_soc tegra30_gr3d_soc = {
.version = 0x30,
+ .num_clocks = 2,
+ .num_resets = 4,
};

static const struct gr3d_soc tegra114_gr3d_soc = {
.version = 0x35,
+ .num_clocks = 1,
+ .num_resets = 2,
};

static const struct of_device_id tegra_gr3d_match[] = {
@@ -278,69 +307,240 @@ static const u32 gr3d_addr_regs[] = {
GR3D_GLOBAL_SAMP23SURFADDR(15),
};

-static int gr3d_probe(struct platform_device *pdev)
+static int gr3d_power_up_legacy_domain(struct device *dev, const char *name,
+ unsigned int id)
{
- struct device_node *np = pdev->dev.of_node;
- struct host1x_syncpt **syncpts;
- struct gr3d *gr3d;
+ struct gr3d *gr3d = dev_get_drvdata(dev);
+ struct reset_control *reset;
+ struct clk *clk;
unsigned int i;
int err;

- gr3d = devm_kzalloc(&pdev->dev, sizeof(*gr3d), GFP_KERNEL);
- if (!gr3d)
- return -ENOMEM;
-
- gr3d->soc = of_device_get_match_data(&pdev->dev);
+ /*
+ * Tegra20 device-tree doesn't specify 3d clock name and there is only
+ * one clock for Tegra20. Tegra30+ device-trees always specified names
+ * for the clocks.
+ */
+ if (gr3d->nclocks == 1) {
+ if (id == TEGRA_POWERGATE_3D1)
+ return 0;
+
+ clk = gr3d->clocks[0].clk;
+ } else {
+ for (i = 0; i < gr3d->nclocks; i++) {
+ if (WARN_ON(!gr3d->clocks[i].id))
+ continue;
+
+ if (!strcmp(gr3d->clocks[i].id, name)) {
+ clk = gr3d->clocks[i].clk;
+ break;
+ }
+ }

- syncpts = devm_kzalloc(&pdev->dev, sizeof(*syncpts), GFP_KERNEL);
- if (!syncpts)
- return -ENOMEM;
+ if (WARN_ON(i == gr3d->nclocks))
+ return -EINVAL;
+ }

- gr3d->clk = devm_clk_get(&pdev->dev, NULL);
- if (IS_ERR(gr3d->clk)) {
- dev_err(&pdev->dev, "cannot get clock\n");
- return PTR_ERR(gr3d->clk);
+ /*
+ * We use array of resets, which includes MC resets, and MC
+ * reset shouldn't be asserted while hardware is gated because
+ * MC flushing will fail for gated hardware. Hence for legacy
+ * PD we request the individual reset separately.
+ */
+ reset = reset_control_get_exclusive_released(dev, name);
+ if (IS_ERR(reset))
+ return PTR_ERR(reset);
+
+ err = reset_control_acquire(reset);
+ if (err) {
+ dev_err(dev, "failed to acquire %s reset: %d\n", name, err);
+ } else {
+ err = tegra_powergate_sequence_power_up(id, clk, reset);
+ reset_control_release(reset);
}

- gr3d->rst = devm_reset_control_get(&pdev->dev, "3d");
- if (IS_ERR(gr3d->rst)) {
- dev_err(&pdev->dev, "cannot get reset\n");
- return PTR_ERR(gr3d->rst);
+ reset_control_put(reset);
+ if (err)
+ return err;
+
+ /*
+ * tegra_powergate_sequence_power_up() leaves clocks enabled
+ * while GENPD not, hence keep clock-enable balanced.
+ */
+ clk_disable_unprepare(clk);
+
+ return 0;
+}
+
+static void gr3d_del_link(void *link)
+{
+ device_link_del(link);
+}
+
+static int gr3d_init_power(struct device *dev, struct gr3d *gr3d)
+{
+ static const char * const opp_genpd_names[] = { "3d0", "3d1", NULL };
+ const u32 link_flags = DL_FLAG_STATELESS | DL_FLAG_PM_RUNTIME;
+ struct device **opp_virt_devs, *pd_dev;
+ struct device_link *link;
+ unsigned int i;
+ int err;
+
+ err = of_count_phandle_with_args(dev->of_node, "power-domains",
+ "#power-domain-cells");
+ if (err < 0) {
+ if (err != -ENOENT)
+ return err;
+
+ /*
+ * Older device-trees don't use GENPD. In this case we should
+ * toggle power domain manually.
+ */
+ err = gr3d_power_up_legacy_domain(dev, "3d",
+ TEGRA_POWERGATE_3D);
+ if (err)
+ return err;
+
+ err = gr3d_power_up_legacy_domain(dev, "3d2",
+ TEGRA_POWERGATE_3D1);
+ if (err)
+ return err;
+
+ return 0;
}

- if (of_device_is_compatible(np, "nvidia,tegra30-gr3d")) {
- gr3d->clk_secondary = devm_clk_get(&pdev->dev, "3d2");
- if (IS_ERR(gr3d->clk_secondary)) {
- dev_err(&pdev->dev, "cannot get secondary clock\n");
- return PTR_ERR(gr3d->clk_secondary);
+ /*
+ * The PM domain core automatically attaches a single power domain,
+ * otherwise it skips attaching completely. We have a single domain
+ * on Tegra20 and two domains on Tegra30+.
+ */
+ if (dev->pm_domain)
+ return 0;
+
+ err = devm_pm_opp_attach_genpd(dev, opp_genpd_names, &opp_virt_devs);
+ if (err)
+ return err;
+
+ for (i = 0; opp_genpd_names[i]; i++) {
+ pd_dev = opp_virt_devs[i];
+ if (!pd_dev) {
+ dev_err(dev, "failed to get %s power domain\n",
+ opp_genpd_names[i]);
+ return -EINVAL;
}

- gr3d->rst_secondary = devm_reset_control_get(&pdev->dev,
- "3d2");
- if (IS_ERR(gr3d->rst_secondary)) {
- dev_err(&pdev->dev, "cannot get secondary reset\n");
- return PTR_ERR(gr3d->rst_secondary);
+ link = device_link_add(dev, pd_dev, link_flags);
+ if (!link) {
+ dev_err(dev, "failed to link to %s\n", dev_name(pd_dev));
+ return -EINVAL;
}
+
+ err = devm_add_action_or_reset(dev, gr3d_del_link, link);
+ if (err)
+ return err;
}

- err = tegra_powergate_sequence_power_up(TEGRA_POWERGATE_3D, gr3d->clk,
- gr3d->rst);
+ return 0;
+}
+
+static int gr3d_set_opp(struct dev_pm_set_opp_data *data)
+{
+ struct gr3d *gr3d = dev_get_drvdata(data->dev);
+ unsigned int i;
+ int err;
+
+ for (i = 0; i < gr3d->nclocks; i++) {
+ err = clk_set_rate(gr3d->clocks[i].clk, data->new_opp.rate);
+ if (err) {
+ dev_err(data->dev, "failed to set %s rate to %lu: %d\n",
+ gr3d->clocks[i].id, data->new_opp.rate, err);
+ goto restore;
+ }
+ }
+
+ return 0;
+
+restore:
+ while (i--)
+ clk_set_rate(gr3d->clocks[i].clk, data->old_opp.rate);
+
+ return err;
+}
+
+static int gr3d_get_clocks(struct device *dev, struct gr3d *gr3d)
+{
+ int err;
+
+ err = devm_clk_bulk_get_all(dev, &gr3d->clocks);
if (err < 0) {
- dev_err(&pdev->dev, "failed to power up 3D unit\n");
+ dev_err(dev, "failed to get clock: %d\n", err);
return err;
}
+ gr3d->nclocks = err;

- if (gr3d->clk_secondary) {
- err = tegra_powergate_sequence_power_up(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;
- }
+ if (gr3d->nclocks != gr3d->soc->num_clocks) {
+ dev_err(dev, "invalid number of clocks: %u\n", gr3d->nclocks);
+ return -ENOENT;
}

+ return 0;
+}
+
+static int gr3d_get_resets(struct device *dev, struct gr3d *gr3d)
+{
+ int err;
+
+ gr3d->resets[RST_MC].id = "mc";
+ gr3d->resets[RST_MC2].id = "mc2";
+ gr3d->resets[RST_GR3D].id = "3d";
+ gr3d->resets[RST_GR3D2].id = "3d2";
+ gr3d->nresets = gr3d->soc->num_resets;
+
+ err = devm_reset_control_bulk_get_optional_exclusive_released(
+ dev, gr3d->nresets, gr3d->resets);
+ if (err) {
+ dev_err(dev, "failed to get reset: %d\n", err);
+ return err;
+ }
+
+ if (WARN_ON(!gr3d->resets[RST_GR3D].rstc) ||
+ WARN_ON(!gr3d->resets[RST_GR3D2].rstc && gr3d->nresets == 4))
+ return -ENOENT;
+
+ return 0;
+}
+
+static int gr3d_probe(struct platform_device *pdev)
+{
+ struct host1x_syncpt **syncpts;
+ struct gr3d *gr3d;
+ unsigned int i;
+ int err;
+
+ gr3d = devm_kzalloc(&pdev->dev, sizeof(*gr3d), GFP_KERNEL);
+ if (!gr3d)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, gr3d);
+
+ gr3d->soc = of_device_get_match_data(&pdev->dev);
+
+ syncpts = devm_kzalloc(&pdev->dev, sizeof(*syncpts), GFP_KERNEL);
+ if (!syncpts)
+ return -ENOMEM;
+
+ err = gr3d_get_clocks(&pdev->dev, gr3d);
+ if (err)
+ return err;
+
+ err = gr3d_get_resets(&pdev->dev, gr3d);
+ if (err)
+ return err;
+
+ err = gr3d_init_power(&pdev->dev, gr3d);
+ if (err)
+ return err;
+
INIT_LIST_HEAD(&gr3d->client.base.list);
gr3d->client.base.ops = &gr3d_client_ops;
gr3d->client.base.dev = &pdev->dev;
@@ -352,20 +552,36 @@ static int gr3d_probe(struct platform_device *pdev)
gr3d->client.version = gr3d->soc->version;
gr3d->client.ops = &gr3d_ops;

+ pm_runtime_enable(&pdev->dev);
+ pm_runtime_use_autosuspend(&pdev->dev);
+ pm_runtime_set_autosuspend_delay(&pdev->dev, 200);
+
+ err = devm_pm_opp_register_set_opp_helper(&pdev->dev, gr3d_set_opp);
+ if (err)
+ goto disable_rpm;
+
+ err = devm_tegra_core_dev_init_opp_table_common(&pdev->dev);
+ if (err)
+ goto disable_rpm;
+
err = host1x_client_register(&gr3d->client.base);
if (err < 0) {
dev_err(&pdev->dev, "failed to register host1x client: %d\n",
err);
- return err;
+ goto disable_rpm;
}

/* initialize address register map */
for (i = 0; i < ARRAY_SIZE(gr3d_addr_regs); i++)
set_bit(gr3d_addr_regs[i], gr3d->addr_regs);

- platform_set_drvdata(pdev, gr3d);
-
return 0;
+
+disable_rpm:
+ pm_runtime_dont_use_autosuspend(&pdev->dev);
+ pm_runtime_disable(&pdev->dev);
+
+ return err;
}

static int gr3d_remove(struct platform_device *pdev)
@@ -380,23 +596,83 @@ static int gr3d_remove(struct platform_device *pdev)
return err;
}

- if (gr3d->clk_secondary) {
- reset_control_assert(gr3d->rst_secondary);
- tegra_powergate_power_off(TEGRA_POWERGATE_3D1);
- clk_disable_unprepare(gr3d->clk_secondary);
+ pm_runtime_dont_use_autosuspend(&pdev->dev);
+ pm_runtime_disable(&pdev->dev);
+
+ return 0;
+}
+
+static int __maybe_unused gr3d_runtime_suspend(struct device *dev)
+{
+ struct gr3d *gr3d = dev_get_drvdata(dev);
+ int err;
+
+ host1x_channel_stop(gr3d->channel);
+
+ err = reset_control_bulk_assert(gr3d->nresets, gr3d->resets);
+ if (err) {
+ dev_err(dev, "failed to assert reset: %d\n", err);
+ return err;
+ }
+
+ usleep_range(10, 20);
+
+ /*
+ * Older device-trees don't specify MC resets and power-gating can't
+ * be done safely in that case. Hence we will keep the power ungated
+ * for older DTBs. For newer DTBs, GENPD will perform the power-gating.
+ */
+
+ clk_bulk_disable_unprepare(gr3d->nclocks, gr3d->clocks);
+ reset_control_bulk_release(gr3d->nresets, gr3d->resets);
+
+ return 0;
+}
+
+static int __maybe_unused gr3d_runtime_resume(struct device *dev)
+{
+ struct gr3d *gr3d = dev_get_drvdata(dev);
+ int err;
+
+ err = reset_control_bulk_acquire(gr3d->nresets, gr3d->resets);
+ if (err) {
+ dev_err(dev, "failed to acquire reset: %d\n", err);
+ return err;
+ }
+
+ err = clk_bulk_prepare_enable(gr3d->nclocks, gr3d->clocks);
+ if (err) {
+ dev_err(dev, "failed to enable clock: %d\n", err);
+ goto release_reset;
}

- reset_control_assert(gr3d->rst);
- tegra_powergate_power_off(TEGRA_POWERGATE_3D);
- clk_disable_unprepare(gr3d->clk);
+ err = reset_control_bulk_deassert(gr3d->nresets, gr3d->resets);
+ if (err) {
+ dev_err(dev, "failed to deassert reset: %d\n", err);
+ goto disable_clk;
+ }

return 0;
+
+disable_clk:
+ clk_bulk_disable_unprepare(gr3d->nclocks, gr3d->clocks);
+release_reset:
+ reset_control_bulk_release(gr3d->nresets, gr3d->resets);
+
+ return err;
}

+static const struct dev_pm_ops tegra_gr3d_pm = {
+ SET_RUNTIME_PM_OPS(gr3d_runtime_suspend, gr3d_runtime_resume, NULL)
+ SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
+ pm_runtime_force_resume)
+};
+
struct platform_driver tegra_gr3d_driver = {
.driver = {
.name = "tegra-gr3d",
.of_match_table = tegra_gr3d_match,
+ .pm = &tegra_gr3d_pm,
},
.probe = gr3d_probe,
.remove = gr3d_remove,
--
2.32.0

2021-09-26 22:45:58

by Dmitry Osipenko

[permalink] [raw]
Subject: [PATCH v13 11/35] drm/tegra: dc: Support OPP and SoC core voltage scaling

Add OPP and SoC core voltage scaling support to the display controller
driver. This is required for enabling system-wide DVFS on pre-Tegra186
SoCs.

Tested-by: Peter Geis <[email protected]> # Ouya T30
Tested-by: Paul Fertser <[email protected]> # PAZ00 T20
Tested-by: Nicolas Chauvet <[email protected]> # PAZ00 T20 and TK1 T124
Tested-by: Matt Merhar <[email protected]> # Ouya T30
Signed-off-by: Dmitry Osipenko <[email protected]>
---
drivers/gpu/drm/tegra/dc.c | 74 ++++++++++++++++++++++++++++++++++++++
drivers/gpu/drm/tegra/dc.h | 2 ++
2 files changed, 76 insertions(+)

diff --git a/drivers/gpu/drm/tegra/dc.c b/drivers/gpu/drm/tegra/dc.c
index a29d64f87563..d4047a14e2b6 100644
--- a/drivers/gpu/drm/tegra/dc.c
+++ b/drivers/gpu/drm/tegra/dc.c
@@ -11,9 +11,12 @@
#include <linux/interconnect.h>
#include <linux/module.h>
#include <linux/of_device.h>
+#include <linux/pm_domain.h>
+#include <linux/pm_opp.h>
#include <linux/pm_runtime.h>
#include <linux/reset.h>

+#include <soc/tegra/common.h>
#include <soc/tegra/pmc.h>

#include <drm/drm_atomic.h>
@@ -1762,6 +1765,47 @@ int tegra_dc_state_setup_clock(struct tegra_dc *dc,
return 0;
}

+static void tegra_dc_update_voltage_state(struct tegra_dc *dc,
+ struct tegra_dc_state *state)
+{
+ unsigned long rate, pstate;
+ struct dev_pm_opp *opp;
+ int err;
+
+ if (!dc->has_opp_table)
+ return;
+
+ /* calculate actual pixel clock rate which depends on internal divider */
+ rate = DIV_ROUND_UP(clk_get_rate(dc->clk) * 2, state->div + 2);
+
+ /* find suitable OPP for the rate */
+ opp = dev_pm_opp_find_freq_ceil(dc->dev, &rate);
+
+ if (opp == ERR_PTR(-ERANGE))
+ opp = dev_pm_opp_find_freq_floor(dc->dev, &rate);
+
+ if (IS_ERR(opp)) {
+ dev_err(dc->dev, "failed to find OPP for %luHz: %pe\n",
+ rate, opp);
+ return;
+ }
+
+ pstate = dev_pm_opp_get_required_pstate(opp, 0);
+ dev_pm_opp_put(opp);
+
+ /*
+ * The minimum core voltage depends on the pixel clock rate (which
+ * depends on internal clock divider of the CRTC) and not on the
+ * rate of the display controller clock. This is why we're not using
+ * dev_pm_opp_set_rate() API and instead controlling the power domain
+ * directly.
+ */
+ err = dev_pm_genpd_set_performance_state(dc->dev, pstate);
+ if (err)
+ dev_err(dc->dev, "failed to set power domain state to %lu: %d\n",
+ pstate, err);
+}
+
static void tegra_dc_commit_state(struct tegra_dc *dc,
struct tegra_dc_state *state)
{
@@ -1801,6 +1845,8 @@ static void tegra_dc_commit_state(struct tegra_dc *dc,
value = SHIFT_CLK_DIVIDER(state->div) | PIXEL_CLK_DIVIDER_PCD1;
tegra_dc_writel(dc, value, DC_DISP_DISP_CLOCK_CONTROL);
}
+
+ tegra_dc_update_voltage_state(dc, state);
}

static void tegra_dc_stop(struct tegra_dc *dc)
@@ -1991,6 +2037,13 @@ static void tegra_crtc_atomic_disable(struct drm_crtc *crtc,
err = host1x_client_suspend(&dc->client);
if (err < 0)
dev_err(dc->dev, "failed to suspend: %d\n", err);
+
+ if (dc->has_opp_table) {
+ err = dev_pm_genpd_set_performance_state(dc->dev, 0);
+ if (err)
+ dev_err(dc->dev,
+ "failed to clear power domain state: %d\n", err);
+ }
}

static void tegra_crtc_atomic_enable(struct drm_crtc *crtc,
@@ -2973,6 +3026,23 @@ static int tegra_dc_couple(struct tegra_dc *dc)
return 0;
}

+static int tegra_dc_init_opp_table(struct tegra_dc *dc)
+{
+ struct tegra_core_opp_params opp_params = {};
+ int err;
+
+ err = devm_tegra_core_dev_init_opp_table(dc->dev, &opp_params);
+ if (err && err != -ENODEV)
+ return err;
+
+ if (err)
+ dc->has_opp_table = false;
+ else
+ dc->has_opp_table = true;
+
+ return 0;
+}
+
static int tegra_dc_probe(struct platform_device *pdev)
{
u64 dma_mask = dma_get_mask(pdev->dev.parent);
@@ -3038,6 +3108,10 @@ static int tegra_dc_probe(struct platform_device *pdev)
tegra_powergate_power_off(dc->powergate);
}

+ err = tegra_dc_init_opp_table(dc);
+ if (err < 0)
+ return err;
+
dc->regs = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(dc->regs))
return PTR_ERR(dc->regs);
diff --git a/drivers/gpu/drm/tegra/dc.h b/drivers/gpu/drm/tegra/dc.h
index 40378308d527..871cfb0cd31c 100644
--- a/drivers/gpu/drm/tegra/dc.h
+++ b/drivers/gpu/drm/tegra/dc.h
@@ -100,6 +100,8 @@ struct tegra_dc {
struct drm_info_list *debugfs_files;

const struct tegra_dc_soc_info *soc;
+
+ bool has_opp_table;
};

static inline struct tegra_dc *
--
2.32.0

2021-09-26 22:46:06

by Dmitry Osipenko

[permalink] [raw]
Subject: [PATCH v13 06/35] clk: tegra: Support runtime PM and power domain

The Clock-and-Reset controller resides in a core power domain on NVIDIA
Tegra SoCs. In order to support voltage scaling of the core power domain,
we hook up DVFS-capable clocks to the core GENPD for managing of the
GENPD's performance state based on the clock changes.

Some clocks don't have any specific physical hardware unit that backs
them, like root PLLs and system clock and they have theirs own voltage
requirements. This patch adds new clk-device driver that backs the clocks
and provides runtime PM functionality for them. A virtual clk-device is
created for each such DVFS-capable clock at the clock's registration time
by the new tegra_clk_register() helper. Driver changes clock's device
GENPD performance state based on clk-rate notifications.

In result we have this sequence of events:

1. Clock driver creates virtual device for selective clocks, enables
runtime PM for the created device and registers the clock.
2. Clk-device driver starts to listen to clock rate changes.
3. Something changes clk rate or enables/disables clk.
4. CCF core propagates the change through the clk tree.
5. Clk-device driver gets clock rate-change notification or GENPD core
handles prepare/unprepare of the clock.
6. Clk-device driver changes GENPD performance state on clock rate
change.
7. GENPD driver changes voltage regulator state change.
8. The regulator state is committed to hardware via I2C.

We rely on fact that DVFS is not needed for Tegra I2C and that Tegra I2C
driver already keeps clock always-prepared. Hence I2C subsystem stays
independent from the clk power management and there are no deadlock spots
in the sequence.

Currently all clocks are registered very early during kernel boot when the
device driver core isn't available yet. The clk-device can't be created
at that time. This patch splits the registration of the clocks in two
phases:

1. Register all essential clocks which don't use RPM and are needed
during early boot.

2. Register at a later boot time the rest of clocks.

This patch adds power management support for Tegra20 and Tegra30 clocks.

Tested-by: Peter Geis <[email protected]> # Ouya T30
Tested-by: Paul Fertser <[email protected]> # PAZ00 T20
Tested-by: Nicolas Chauvet <[email protected]> # PAZ00 T20 and TK1 T124
Tested-by: Matt Merhar <[email protected]> # Ouya T30
Signed-off-by: Dmitry Osipenko <[email protected]>
---
drivers/clk/tegra/Makefile | 1 +
drivers/clk/tegra/clk-device.c | 230 ++++++++++++++++++++++++++++++++
drivers/clk/tegra/clk-pll.c | 2 +-
drivers/clk/tegra/clk-super.c | 2 +-
drivers/clk/tegra/clk-tegra20.c | 77 ++++++++---
drivers/clk/tegra/clk-tegra30.c | 116 +++++++++++-----
drivers/clk/tegra/clk.c | 75 ++++++++++-
drivers/clk/tegra/clk.h | 2 +
8 files changed, 451 insertions(+), 54 deletions(-)
create mode 100644 drivers/clk/tegra/clk-device.c

diff --git a/drivers/clk/tegra/Makefile b/drivers/clk/tegra/Makefile
index 7b1816856eb5..a0715cdfc1a4 100644
--- a/drivers/clk/tegra/Makefile
+++ b/drivers/clk/tegra/Makefile
@@ -1,6 +1,7 @@
# SPDX-License-Identifier: GPL-2.0
obj-y += clk.o
obj-y += clk-audio-sync.o
+obj-y += clk-device.o
obj-y += clk-dfll.o
obj-y += clk-divider.o
obj-y += clk-periph.o
diff --git a/drivers/clk/tegra/clk-device.c b/drivers/clk/tegra/clk-device.c
new file mode 100644
index 000000000000..830bc0ba25d3
--- /dev/null
+++ b/drivers/clk/tegra/clk-device.c
@@ -0,0 +1,230 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/mutex.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/pm_domain.h>
+#include <linux/pm_opp.h>
+#include <linux/pm_runtime.h>
+#include <linux/slab.h>
+
+#include <soc/tegra/common.h>
+
+#include "clk.h"
+
+/*
+ * This driver manages performance state of the core power domain for the
+ * independent PLLs and system clocks. We created a virtual clock device
+ * for such clocks, see tegra_clk_dev_register().
+ */
+
+struct tegra_clk_device {
+ struct notifier_block clk_nb;
+ struct device *dev;
+ struct clk_hw *hw;
+ struct mutex lock;
+};
+
+static int tegra_clock_set_pd_state(struct tegra_clk_device *clk_dev,
+ unsigned long rate)
+{
+ struct device *dev = clk_dev->dev;
+ struct dev_pm_opp *opp;
+ unsigned int pstate;
+
+ opp = dev_pm_opp_find_freq_ceil(dev, &rate);
+ if (opp == ERR_PTR(-ERANGE)) {
+ dev_dbg(dev, "failed to find ceil OPP for %luHz\n", rate);
+ opp = dev_pm_opp_find_freq_floor(dev, &rate);
+ }
+
+ if (IS_ERR(opp)) {
+ dev_err(dev, "failed to find OPP for %luHz: %pe\n", rate, opp);
+ return PTR_ERR(opp);
+ }
+
+ pstate = dev_pm_opp_get_required_pstate(opp, 0);
+ dev_pm_opp_put(opp);
+
+ return dev_pm_genpd_set_performance_state(dev, pstate);
+}
+
+static int tegra_clock_change_notify(struct notifier_block *nb,
+ unsigned long msg, void *data)
+{
+ struct clk_notifier_data *cnd = data;
+ struct tegra_clk_device *clk_dev;
+ int err = 0;
+
+ clk_dev = container_of(nb, struct tegra_clk_device, clk_nb);
+
+ mutex_lock(&clk_dev->lock);
+ switch (msg) {
+ case PRE_RATE_CHANGE:
+ if (cnd->new_rate > cnd->old_rate)
+ err = tegra_clock_set_pd_state(clk_dev, cnd->new_rate);
+ break;
+
+ case ABORT_RATE_CHANGE:
+ err = tegra_clock_set_pd_state(clk_dev, cnd->old_rate);
+ break;
+
+ case POST_RATE_CHANGE:
+ if (cnd->new_rate < cnd->old_rate)
+ err = tegra_clock_set_pd_state(clk_dev, cnd->new_rate);
+ break;
+
+ default:
+ break;
+ }
+ mutex_unlock(&clk_dev->lock);
+
+ return notifier_from_errno(err);
+}
+
+static int tegra_clock_sync_pd_state(struct tegra_clk_device *clk_dev)
+{
+ unsigned long rate;
+ int ret = 0;
+
+ mutex_lock(&clk_dev->lock);
+
+ if (!pm_runtime_status_suspended(clk_dev->dev)) {
+ rate = clk_hw_get_rate(clk_dev->hw);
+ ret = tegra_clock_set_pd_state(clk_dev, rate);
+ }
+
+ mutex_unlock(&clk_dev->lock);
+
+ return ret;
+}
+
+static int tegra_clock_probe(struct platform_device *pdev)
+{
+ struct tegra_core_opp_params opp_params = {};
+ struct tegra_clk_device *clk_dev;
+ struct device *dev = &pdev->dev;
+ struct clk *clk;
+ int err;
+
+ if (!dev->pm_domain)
+ return -EINVAL;
+
+ clk_dev = devm_kzalloc(dev, sizeof(*clk_dev), GFP_KERNEL);
+ if (!clk_dev)
+ return -ENOMEM;
+
+ clk = devm_clk_get(dev, NULL);
+ if (IS_ERR(clk))
+ return PTR_ERR(clk);
+
+ clk_dev->dev = dev;
+ clk_dev->hw = __clk_get_hw(clk);
+ clk_dev->clk_nb.notifier_call = tegra_clock_change_notify;
+ mutex_init(&clk_dev->lock);
+
+ platform_set_drvdata(pdev, clk_dev);
+
+ /*
+ * Runtime PM was already enabled for this device by the parent clk
+ * driver and power domain state should be synced under clk_dev lock,
+ * hence we don't use the common OPP helper that initializes OPP
+ * state. For some clocks common OPP helper may fail to find ceil
+ * rate, it's handled by this driver.
+ */
+ err = devm_tegra_core_dev_init_opp_table(dev, &opp_params);
+ if (err)
+ return err;
+
+ err = clk_notifier_register(clk, &clk_dev->clk_nb);
+ if (err) {
+ dev_err(dev, "failed to register clk notifier: %d\n", err);
+ return err;
+ }
+
+ /*
+ * The driver is attaching to a potentially active/resumed clock, hence
+ * we need to sync the power domain performance state in a accordance to
+ * the clock rate if clock is resumed.
+ */
+ err = tegra_clock_sync_pd_state(clk_dev);
+ if (err)
+ goto unreg_clk;
+
+ return 0;
+
+unreg_clk:
+ clk_notifier_unregister(clk, &clk_dev->clk_nb);
+
+ return err;
+}
+
+static __maybe_unused int tegra_clock_pm_suspend(struct device *dev)
+{
+ struct tegra_clk_device *clk_dev = dev_get_drvdata(dev);
+
+ /*
+ * Power management of the clock is entangled with the Tegra PMC
+ * GENPD because PMC driver enables/disables clocks for toggling
+ * of the PD's on/off state.
+ *
+ * The PMC GENPD is resumed in NOIRQ phase, before RPM of the clocks
+ * becomes available, hence PMC can't use clocks at the early resume
+ * phase if RPM is involved. For example when 3d clock is enabled,
+ * it may enable the parent PLL clock that needs to be RPM-resumed.
+ *
+ * Secondly, the PLL clocks may be enabled by the low level suspend
+ * code, so we need to assume that PLL is in enabled state during
+ * suspend.
+ *
+ * We will keep PLLs and system clock resumed during suspend time.
+ * All PLLs on all SoCs are low power and system clock is always-on,
+ * so practically not much is changed here.
+ */
+
+ return clk_prepare(clk_dev->hw->clk);
+}
+
+static __maybe_unused int tegra_clock_pm_resume(struct device *dev)
+{
+ struct tegra_clk_device *clk_dev = dev_get_drvdata(dev);
+
+ clk_unprepare(clk_dev->hw->clk);
+
+ return 0;
+}
+
+static void tegra_clock_shutdown(struct platform_device *pdev)
+{
+ struct tegra_clk_device *clk_dev = platform_get_drvdata(pdev);
+
+ clk_prepare(clk_dev->hw->clk);
+}
+
+static const struct dev_pm_ops tegra_clock_pm = {
+ SET_SYSTEM_SLEEP_PM_OPS(tegra_clock_pm_suspend,
+ tegra_clock_pm_resume)
+};
+
+static const struct of_device_id tegra_clock_match[] = {
+ { .compatible = "nvidia,tegra20-sclk" },
+ { .compatible = "nvidia,tegra30-sclk" },
+ { .compatible = "nvidia,tegra30-pllc" },
+ { .compatible = "nvidia,tegra30-plle" },
+ { .compatible = "nvidia,tegra30-pllm" },
+ { }
+};
+
+static struct platform_driver tegra_clock_driver = {
+ .driver = {
+ .name = "tegra-clock",
+ .of_match_table = tegra_clock_match,
+ .pm = &tegra_clock_pm,
+ .suppress_bind_attrs = true,
+ },
+ .probe = tegra_clock_probe,
+ .shutdown = tegra_clock_shutdown,
+};
+builtin_platform_driver(tegra_clock_driver);
diff --git a/drivers/clk/tegra/clk-pll.c b/drivers/clk/tegra/clk-pll.c
index eaa079c177c3..100b5d9b7e26 100644
--- a/drivers/clk/tegra/clk-pll.c
+++ b/drivers/clk/tegra/clk-pll.c
@@ -1914,7 +1914,7 @@ static struct clk *_tegra_clk_register_pll(struct tegra_clk_pll *pll,
/* Data in .init is copied by clk_register(), so stack variable OK */
pll->hw.init = &init;

- return clk_register(NULL, &pll->hw);
+ return tegra_clk_dev_register(&pll->hw);
}

struct clk *tegra_clk_register_pll(const char *name, const char *parent_name,
diff --git a/drivers/clk/tegra/clk-super.c b/drivers/clk/tegra/clk-super.c
index 6099c6e9acd4..a98a420398fa 100644
--- a/drivers/clk/tegra/clk-super.c
+++ b/drivers/clk/tegra/clk-super.c
@@ -226,7 +226,7 @@ struct clk *tegra_clk_register_super_mux(const char *name,
/* Data in .init is copied by clk_register(), so stack variable OK */
super->hw.init = &init;

- clk = clk_register(NULL, &super->hw);
+ clk = tegra_clk_dev_register(&super->hw);
if (IS_ERR(clk))
kfree(super);

diff --git a/drivers/clk/tegra/clk-tegra20.c b/drivers/clk/tegra/clk-tegra20.c
index 3664593a5ba4..be3c33441cfc 100644
--- a/drivers/clk/tegra/clk-tegra20.c
+++ b/drivers/clk/tegra/clk-tegra20.c
@@ -6,8 +6,11 @@
#include <linux/io.h>
#include <linux/clk-provider.h>
#include <linux/clkdev.h>
+#include <linux/init.h>
#include <linux/of.h>
#include <linux/of_address.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
#include <linux/clk/tegra.h>
#include <linux/delay.h>
#include <dt-bindings/clock/tegra20-car.h>
@@ -414,7 +417,7 @@ static struct tegra_clk_pll_params pll_e_params = {
.fixed_rate = 100000000,
};

-static struct tegra_devclk devclks[] __initdata = {
+static struct tegra_devclk devclks[] = {
{ .con_id = "pll_c", .dt_id = TEGRA20_CLK_PLL_C },
{ .con_id = "pll_c_out1", .dt_id = TEGRA20_CLK_PLL_C_OUT1 },
{ .con_id = "pll_p", .dt_id = TEGRA20_CLK_PLL_P },
@@ -710,13 +713,6 @@ static void tegra20_super_clk_init(void)
NULL);
clks[TEGRA20_CLK_CCLK] = clk;

- /* SCLK */
- clk = tegra_clk_register_super_mux("sclk", sclk_parents,
- ARRAY_SIZE(sclk_parents),
- CLK_SET_RATE_PARENT | CLK_IS_CRITICAL,
- clk_base + SCLK_BURST_POLICY, 0, 4, 0, 0, NULL);
- clks[TEGRA20_CLK_SCLK] = clk;
-
/* twd */
clk = clk_register_fixed_factor(NULL, "twd", "cclk", 0, 1, 4);
clks[TEGRA20_CLK_TWD] = clk;
@@ -1014,7 +1010,7 @@ static struct tegra_cpu_car_ops tegra20_cpu_car_ops = {
#endif
};

-static struct tegra_clk_init_table init_table[] __initdata = {
+static struct tegra_clk_init_table init_table[] = {
{ TEGRA20_CLK_PLL_P, TEGRA20_CLK_CLK_MAX, 216000000, 1 },
{ TEGRA20_CLK_PLL_P_OUT1, TEGRA20_CLK_CLK_MAX, 28800000, 1 },
{ TEGRA20_CLK_PLL_P_OUT2, TEGRA20_CLK_CLK_MAX, 48000000, 1 },
@@ -1052,11 +1048,6 @@ static struct tegra_clk_init_table init_table[] __initdata = {
{ TEGRA20_CLK_CLK_MAX, TEGRA20_CLK_CLK_MAX, 0, 0 },
};

-static void __init tegra20_clock_apply_init_table(void)
-{
- tegra_init_from_table(init_table, clks, TEGRA20_CLK_CLK_MAX);
-}
-
/*
* Some clocks may be used by different drivers depending on the board
* configuration. List those here to register them twice in the clock lookup
@@ -1076,6 +1067,8 @@ static const struct of_device_id pmc_match[] __initconst = {
{ },
};

+static bool tegra20_car_initialized;
+
static struct clk *tegra20_clk_src_onecell_get(struct of_phandle_args *clkspec,
void *data)
{
@@ -1083,6 +1076,16 @@ static struct clk *tegra20_clk_src_onecell_get(struct of_phandle_args *clkspec,
struct clk_hw *hw;
struct clk *clk;

+ /*
+ * Timer clocks are needed early, the rest of the clocks shouldn't be
+ * available to device drivers until clock tree is fully initialized.
+ */
+ if (clkspec->args[0] != TEGRA20_CLK_RTC &&
+ clkspec->args[0] != TEGRA20_CLK_TWD &&
+ clkspec->args[0] != TEGRA20_CLK_TIMER &&
+ !tegra20_car_initialized)
+ return ERR_PTR(-EPROBE_DEFER);
+
clk = of_clk_src_onecell_get(clkspec, data);
if (IS_ERR(clk))
return clk;
@@ -1149,10 +1152,48 @@ static void __init tegra20_clock_init(struct device_node *np)
tegra_init_dup_clks(tegra_clk_duplicates, clks, TEGRA20_CLK_CLK_MAX);

tegra_add_of_provider(np, tegra20_clk_src_onecell_get);
- tegra_register_devclks(devclks, ARRAY_SIZE(devclks));
-
- tegra_clk_apply_init_table = tegra20_clock_apply_init_table;

tegra_cpu_car_ops = &tegra20_cpu_car_ops;
}
-CLK_OF_DECLARE(tegra20, "nvidia,tegra20-car", tegra20_clock_init);
+CLK_OF_DECLARE_DRIVER(tegra20, "nvidia,tegra20-car", tegra20_clock_init);
+
+/*
+ * Clocks that use runtime PM can't be created at the tegra20_clock_init
+ * time because drivers' base isn't initialized yet, and thus platform
+ * devices can't be created for the clocks. Hence we need to split the
+ * registration of the clocks into two phases. The first phase registers
+ * essential clocks which don't require RPM and are actually used during
+ * early boot. The second phase registers clocks which use RPM and this
+ * is done when device drivers' core API is ready.
+ */
+static int tegra20_car_probe(struct platform_device *pdev)
+{
+ struct clk *clk;
+
+ clk = tegra_clk_register_super_mux("sclk", sclk_parents,
+ ARRAY_SIZE(sclk_parents),
+ CLK_SET_RATE_PARENT | CLK_IS_CRITICAL,
+ clk_base + SCLK_BURST_POLICY, 0, 4, 0, 0, NULL);
+ clks[TEGRA20_CLK_SCLK] = clk;
+
+ tegra_register_devclks(devclks, ARRAY_SIZE(devclks));
+ tegra_init_from_table(init_table, clks, TEGRA20_CLK_CLK_MAX);
+ tegra20_car_initialized = true;
+
+ return 0;
+}
+
+static const struct of_device_id tegra20_car_match[] = {
+ { .compatible = "nvidia,tegra20-car" },
+ { }
+};
+
+static struct platform_driver tegra20_car_driver = {
+ .driver = {
+ .name = "tegra20-car",
+ .of_match_table = tegra20_car_match,
+ .suppress_bind_attrs = true,
+ },
+ .probe = tegra20_car_probe,
+};
+builtin_platform_driver(tegra20_car_driver);
diff --git a/drivers/clk/tegra/clk-tegra30.c b/drivers/clk/tegra/clk-tegra30.c
index 64121bc66d85..04b496123820 100644
--- a/drivers/clk/tegra/clk-tegra30.c
+++ b/drivers/clk/tegra/clk-tegra30.c
@@ -7,8 +7,11 @@
#include <linux/delay.h>
#include <linux/clk-provider.h>
#include <linux/clkdev.h>
+#include <linux/init.h>
#include <linux/of.h>
#include <linux/of_address.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
#include <linux/clk/tegra.h>

#include <soc/tegra/pmc.h>
@@ -532,7 +535,7 @@ static unsigned long tegra30_input_freq[] = {
[12] = 26000000,
};

-static struct tegra_devclk devclks[] __initdata = {
+static struct tegra_devclk devclks[] = {
{ .con_id = "pll_c", .dt_id = TEGRA30_CLK_PLL_C },
{ .con_id = "pll_c_out1", .dt_id = TEGRA30_CLK_PLL_C_OUT1 },
{ .con_id = "pll_p", .dt_id = TEGRA30_CLK_PLL_P },
@@ -812,11 +815,6 @@ static void __init tegra30_pll_init(void)
{
struct clk *clk;

- /* PLLC */
- clk = tegra_clk_register_pll("pll_c", "pll_ref", clk_base, pmc_base, 0,
- &pll_c_params, NULL);
- clks[TEGRA30_CLK_PLL_C] = clk;
-
/* PLLC_OUT1 */
clk = tegra_clk_register_divider("pll_c_out1_div", "pll_c",
clk_base + PLLC_OUT, 0, TEGRA_DIVIDER_ROUND_UP,
@@ -826,11 +824,6 @@ static void __init tegra30_pll_init(void)
0, NULL);
clks[TEGRA30_CLK_PLL_C_OUT1] = clk;

- /* PLLM */
- clk = tegra_clk_register_pll("pll_m", "pll_ref", clk_base, pmc_base,
- CLK_SET_RATE_GATE, &pll_m_params, NULL);
- clks[TEGRA30_CLK_PLL_M] = clk;
-
/* PLLM_OUT1 */
clk = tegra_clk_register_divider("pll_m_out1_div", "pll_m",
clk_base + PLLM_OUT, 0, TEGRA_DIVIDER_ROUND_UP,
@@ -880,9 +873,6 @@ static void __init tegra30_pll_init(void)
ARRAY_SIZE(pll_e_parents),
CLK_SET_RATE_NO_REPARENT,
clk_base + PLLE_AUX, 2, 1, 0, NULL);
- clk = tegra_clk_register_plle("pll_e", "pll_e_mux", clk_base, pmc_base,
- CLK_GET_RATE_NOCACHE, &pll_e_params, NULL);
- clks[TEGRA30_CLK_PLL_E] = clk;
}

static const char *cclk_g_parents[] = { "clk_m", "pll_c", "clk_32k", "pll_m",
@@ -971,14 +961,6 @@ static void __init tegra30_super_clk_init(void)
NULL);
clks[TEGRA30_CLK_CCLK_LP] = clk;

- /* SCLK */
- clk = tegra_clk_register_super_mux("sclk", sclk_parents,
- ARRAY_SIZE(sclk_parents),
- CLK_SET_RATE_PARENT | CLK_IS_CRITICAL,
- clk_base + SCLK_BURST_POLICY,
- 0, 4, 0, 0, NULL);
- clks[TEGRA30_CLK_SCLK] = clk;
-
/* twd */
clk = clk_register_fixed_factor(NULL, "twd", "cclk_g",
CLK_SET_RATE_PARENT, 1, 2);
@@ -1214,7 +1196,7 @@ static struct tegra_cpu_car_ops tegra30_cpu_car_ops = {
#endif
};

-static struct tegra_clk_init_table init_table[] __initdata = {
+static struct tegra_clk_init_table init_table[] = {
{ TEGRA30_CLK_UARTA, TEGRA30_CLK_PLL_P, 408000000, 0 },
{ TEGRA30_CLK_UARTB, TEGRA30_CLK_PLL_P, 408000000, 0 },
{ TEGRA30_CLK_UARTC, TEGRA30_CLK_PLL_P, 408000000, 0 },
@@ -1259,11 +1241,6 @@ static struct tegra_clk_init_table init_table[] __initdata = {
{ TEGRA30_CLK_CLK_MAX, TEGRA30_CLK_CLK_MAX, 0, 0 },
};

-static void __init tegra30_clock_apply_init_table(void)
-{
- tegra_init_from_table(init_table, clks, TEGRA30_CLK_CLK_MAX);
-}
-
/*
* Some clocks may be used by different drivers depending on the board
* configuration. List those here to register them twice in the clock lookup
@@ -1294,12 +1271,24 @@ static struct tegra_audio_clk_info tegra30_audio_plls[] = {
{ "pll_a", &pll_a_params, tegra_clk_pll_a, "pll_p_out1" },
};

+static bool tegra30_car_initialized;
+
static struct clk *tegra30_clk_src_onecell_get(struct of_phandle_args *clkspec,
void *data)
{
struct clk_hw *hw;
struct clk *clk;

+ /*
+ * Timer clocks are needed early, the rest of the clocks shouldn't be
+ * available to device drivers until clock tree is fully initialized.
+ */
+ if (clkspec->args[0] != TEGRA30_CLK_RTC &&
+ clkspec->args[0] != TEGRA30_CLK_TWD &&
+ clkspec->args[0] != TEGRA30_CLK_TIMER &&
+ !tegra30_car_initialized)
+ return ERR_PTR(-EPROBE_DEFER);
+
clk = of_clk_src_onecell_get(clkspec, data);
if (IS_ERR(clk))
return clk;
@@ -1357,10 +1346,75 @@ static void __init tegra30_clock_init(struct device_node *np)
tegra_init_dup_clks(tegra_clk_duplicates, clks, TEGRA30_CLK_CLK_MAX);

tegra_add_of_provider(np, tegra30_clk_src_onecell_get);
+
+ tegra_cpu_car_ops = &tegra30_cpu_car_ops;
+}
+CLK_OF_DECLARE_DRIVER(tegra30, "nvidia,tegra30-car", tegra30_clock_init);
+
+/*
+ * Clocks that use runtime PM can't be created at the tegra30_clock_init
+ * time because drivers' base isn't initialized yet, and thus platform
+ * devices can't be created for the clocks. Hence we need to split the
+ * registration of the clocks into two phases. The first phase registers
+ * essential clocks which don't require RPM and are actually used during
+ * early boot. The second phase registers clocks which use RPM and this
+ * is done when device drivers' core API is ready.
+ */
+static int tegra30_car_probe(struct platform_device *pdev)
+{
+ struct clk *clk;
+
+ /* PLLC */
+ clk = tegra_clk_register_pll("pll_c", "pll_ref", clk_base, pmc_base, 0,
+ &pll_c_params, NULL);
+ clks[TEGRA30_CLK_PLL_C] = clk;
+
+ /* PLLE */
+ clk = tegra_clk_register_plle("pll_e", "pll_e_mux", clk_base, pmc_base,
+ CLK_GET_RATE_NOCACHE, &pll_e_params, NULL);
+ clks[TEGRA30_CLK_PLL_E] = clk;
+
+ /* PLLM */
+ clk = tegra_clk_register_pll("pll_m", "pll_ref", clk_base, pmc_base,
+ CLK_SET_RATE_GATE, &pll_m_params, NULL);
+ clks[TEGRA30_CLK_PLL_M] = clk;
+
+ /* SCLK */
+ clk = tegra_clk_register_super_mux("sclk", sclk_parents,
+ ARRAY_SIZE(sclk_parents),
+ CLK_SET_RATE_PARENT | CLK_IS_CRITICAL,
+ clk_base + SCLK_BURST_POLICY,
+ 0, 4, 0, 0, NULL);
+ clks[TEGRA30_CLK_SCLK] = clk;
+
tegra_register_devclks(devclks, ARRAY_SIZE(devclks));
+ tegra_init_from_table(init_table, clks, TEGRA30_CLK_CLK_MAX);
+ tegra30_car_initialized = true;

- tegra_clk_apply_init_table = tegra30_clock_apply_init_table;
+ return 0;
+}

- tegra_cpu_car_ops = &tegra30_cpu_car_ops;
+static const struct of_device_id tegra30_car_match[] = {
+ { .compatible = "nvidia,tegra30-car" },
+ { }
+};
+
+static struct platform_driver tegra30_car_driver = {
+ .driver = {
+ .name = "tegra30-car",
+ .of_match_table = tegra30_car_match,
+ .suppress_bind_attrs = true,
+ },
+ .probe = tegra30_car_probe,
+};
+
+/*
+ * Clock driver must be registered before memory controller driver,
+ * which doesn't support deferred probing for today and is registered
+ * from arch init-level.
+ */
+static int tegra30_car_init(void)
+{
+ return platform_driver_register(&tegra30_car_driver);
}
-CLK_OF_DECLARE(tegra30, "nvidia,tegra30-car", tegra30_clock_init);
+postcore_initcall(tegra30_car_init);
diff --git a/drivers/clk/tegra/clk.c b/drivers/clk/tegra/clk.c
index f6cdce441cf7..26bda45813c0 100644
--- a/drivers/clk/tegra/clk.c
+++ b/drivers/clk/tegra/clk.c
@@ -9,14 +9,19 @@
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/of.h>
+#include <linux/of_device.h>
#include <linux/clk/tegra.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
#include <linux/reset-controller.h>
+#include <linux/string.h>

#include <soc/tegra/fuse.h>

#include "clk.h"

/* Global data of Tegra CPU CAR ops */
+static struct device_node *tegra_car_np;
static struct tegra_cpu_car_ops dummy_car_ops;
struct tegra_cpu_car_ops *tegra_cpu_car_ops = &dummy_car_ops;

@@ -261,8 +266,8 @@ void __init tegra_init_dup_clks(struct tegra_clk_duplicate *dup_list,
}
}

-void __init tegra_init_from_table(struct tegra_clk_init_table *tbl,
- struct clk *clks[], int clk_max)
+void tegra_init_from_table(struct tegra_clk_init_table *tbl,
+ struct clk *clks[], int clk_max)
{
struct clk *clk;

@@ -320,6 +325,8 @@ void __init tegra_add_of_provider(struct device_node *np,
{
int i;

+ tegra_car_np = np;
+
for (i = 0; i < clk_num; i++) {
if (IS_ERR(clks[i])) {
pr_err
@@ -348,7 +355,7 @@ void __init tegra_init_special_resets(unsigned int num,
special_reset_deassert = deassert;
}

-void __init tegra_register_devclks(struct tegra_devclk *dev_clks, int num)
+void tegra_register_devclks(struct tegra_devclk *dev_clks, int num)
{
int i;

@@ -372,6 +379,68 @@ struct clk ** __init tegra_lookup_dt_id(int clk_id,
return NULL;
}

+static struct device_node *tegra_clk_get_of_node(struct clk_hw *hw)
+{
+ struct device_node *np;
+ char *node_name;
+
+ node_name = kstrdup(hw->init->name, GFP_KERNEL);
+ if (!node_name)
+ return NULL;
+
+ strreplace(node_name, '_', '-');
+
+ for_each_child_of_node(tegra_car_np, np) {
+ if (!strcmp(np->name, node_name))
+ break;
+ }
+
+ kfree(node_name);
+
+ return np;
+}
+
+struct clk *tegra_clk_dev_register(struct clk_hw *hw)
+{
+ struct platform_device *pdev, *parent;
+ const char *dev_name = NULL;
+ struct device *dev = NULL;
+ struct device_node *np;
+
+ np = tegra_clk_get_of_node(hw);
+
+ if (!of_device_is_available(np))
+ goto put_node;
+
+ dev_name = kasprintf(GFP_KERNEL, "tegra_clk_%s", hw->init->name);
+ if (!dev_name)
+ goto put_node;
+
+ parent = of_find_device_by_node(tegra_car_np);
+ if (parent) {
+ pdev = of_platform_device_create(np, dev_name, &parent->dev);
+ put_device(&parent->dev);
+
+ if (!pdev) {
+ pr_err("%s: failed to create device for %pOF\n",
+ __func__, np);
+ goto free_name;
+ }
+
+ dev = &pdev->dev;
+ pm_runtime_enable(dev);
+ } else {
+ WARN(1, "failed to find device for %pOF\n", tegra_car_np);
+ }
+
+free_name:
+ kfree(dev_name);
+put_node:
+ of_node_put(np);
+
+ return clk_register(dev, hw);
+}
+
tegra_clk_apply_init_table_func tegra_clk_apply_init_table;

static int __init tegra_clocks_apply_init_table(void)
diff --git a/drivers/clk/tegra/clk.h b/drivers/clk/tegra/clk.h
index 0c3ba0ccce1a..5d80d8b79b8e 100644
--- a/drivers/clk/tegra/clk.h
+++ b/drivers/clk/tegra/clk.h
@@ -927,4 +927,6 @@ struct clk *tegra20_clk_register_emc(void __iomem *ioaddr, bool low_jitter);
struct clk *tegra210_clk_register_emc(struct device_node *np,
void __iomem *regs);

+struct clk *tegra_clk_dev_register(struct clk_hw *hw);
+
#endif /* TEGRA_CLK_H */
--
2.32.0

2021-09-26 22:46:09

by Dmitry Osipenko

[permalink] [raw]
Subject: [PATCH v13 10/35] gpu: host1x: Add host1x_channel_stop()

Add host1x_channel_stop() which waits till channel becomes idle and then
stops the channel hardware. This is needed for supporting suspend/resume
by host1x drivers since the hardware state is lost after power-gating,
thus the channel needs to be stopped before client enters into suspend.

Tested-by: Peter Geis <[email protected]> # Ouya T30
Tested-by: Paul Fertser <[email protected]> # PAZ00 T20
Tested-by: Nicolas Chauvet <[email protected]> # PAZ00 T20 and TK1 T124
Tested-by: Matt Merhar <[email protected]> # Ouya T30
Signed-off-by: Dmitry Osipenko <[email protected]>
---
drivers/gpu/host1x/channel.c | 8 ++++++++
include/linux/host1x.h | 1 +
2 files changed, 9 insertions(+)

diff --git a/drivers/gpu/host1x/channel.c b/drivers/gpu/host1x/channel.c
index 4cd212bb570d..2a9a3a8d5931 100644
--- a/drivers/gpu/host1x/channel.c
+++ b/drivers/gpu/host1x/channel.c
@@ -75,6 +75,14 @@ struct host1x_channel *host1x_channel_get_index(struct host1x *host,
return ch;
}

+void host1x_channel_stop(struct host1x_channel *channel)
+{
+ struct host1x *host = dev_get_drvdata(channel->dev->parent);
+
+ host1x_hw_cdma_stop(host, &channel->cdma);
+}
+EXPORT_SYMBOL(host1x_channel_stop);
+
static void release_channel(struct kref *kref)
{
struct host1x_channel *channel =
diff --git a/include/linux/host1x.h b/include/linux/host1x.h
index 7bccf589aba7..66473b5be0af 100644
--- a/include/linux/host1x.h
+++ b/include/linux/host1x.h
@@ -181,6 +181,7 @@ struct host1x_job;

struct host1x_channel *host1x_channel_request(struct host1x_client *client);
struct host1x_channel *host1x_channel_get(struct host1x_channel *channel);
+void host1x_channel_stop(struct host1x_channel *channel);
void host1x_channel_put(struct host1x_channel *channel);
int host1x_job_submit(struct host1x_job *job);

--
2.32.0

2021-09-26 22:46:15

by Dmitry Osipenko

[permalink] [raw]
Subject: [PATCH v13 12/35] drm/tegra: hdmi: Add OPP support

The HDMI on Tegra belongs to the core power domain and we're going to
enable GENPD support for the core domain. Now HDMI driver must use
OPP API for driving the controller's clock rate because OPP API takes
care of reconfiguring the domain's performance state based on HDMI clock
rate. Add OPP support to the HDMI driver.

Signed-off-by: Dmitry Osipenko <[email protected]>
---
drivers/gpu/drm/tegra/hdmi.c | 16 ++++++++++++----
1 file changed, 12 insertions(+), 4 deletions(-)

diff --git a/drivers/gpu/drm/tegra/hdmi.c b/drivers/gpu/drm/tegra/hdmi.c
index e5d2a4026028..9a87d351a828 100644
--- a/drivers/gpu/drm/tegra/hdmi.c
+++ b/drivers/gpu/drm/tegra/hdmi.c
@@ -11,10 +11,13 @@
#include <linux/math64.h>
#include <linux/module.h>
#include <linux/of_device.h>
+#include <linux/pm_opp.h>
#include <linux/pm_runtime.h>
#include <linux/regulator/consumer.h>
#include <linux/reset.h>

+#include <soc/tegra/common.h>
+
#include <drm/drm_atomic_helper.h>
#include <drm/drm_crtc.h>
#include <drm/drm_debugfs.h>
@@ -1195,7 +1198,7 @@ static void tegra_hdmi_encoder_enable(struct drm_encoder *encoder)
h_back_porch = mode->htotal - mode->hsync_end;
h_front_porch = mode->hsync_start - mode->hdisplay;

- err = clk_set_rate(hdmi->clk, hdmi->pixel_clock);
+ err = dev_pm_opp_set_rate(hdmi->dev, hdmi->pixel_clock);
if (err < 0) {
dev_err(hdmi->dev, "failed to set HDMI clock frequency: %d\n",
err);
@@ -1732,7 +1735,14 @@ static int tegra_hdmi_probe(struct platform_device *pdev)
}

platform_set_drvdata(pdev, hdmi);
- pm_runtime_enable(&pdev->dev);
+
+ err = devm_pm_runtime_enable(&pdev->dev);
+ if (err)
+ return err;
+
+ err = devm_tegra_core_dev_init_opp_table_common(&pdev->dev);
+ if (err)
+ return err;

INIT_LIST_HEAD(&hdmi->client.list);
hdmi->client.ops = &hdmi_client_ops;
@@ -1753,8 +1763,6 @@ static int tegra_hdmi_remove(struct platform_device *pdev)
struct tegra_hdmi *hdmi = platform_get_drvdata(pdev);
int err;

- pm_runtime_disable(&pdev->dev);
-
err = host1x_client_unregister(&hdmi->client);
if (err < 0) {
dev_err(&pdev->dev, "failed to unregister host1x client: %d\n",
--
2.32.0

2021-09-26 22:46:15

by Dmitry Osipenko

[permalink] [raw]
Subject: [PATCH v13 32/35] ARM: tegra: Add OPP tables and power domains to Tegra30 device-trees

Add OPP tables and power domains to all peripheral devices which
support power management on Tegra30 SoC.

Tested-by: Peter Geis <[email protected]> # Ouya T30
Tested-by: Matt Merhar <[email protected]> # Ouya T30
Signed-off-by: Dmitry Osipenko <[email protected]>
---
.../tegra30-asus-nexus7-grouper-common.dtsi | 1 +
arch/arm/boot/dts/tegra30-beaver.dts | 1 +
arch/arm/boot/dts/tegra30-cardhu.dtsi | 1 +
arch/arm/boot/dts/tegra30-colibri.dtsi | 17 +-
arch/arm/boot/dts/tegra30-ouya.dts | 1 +
.../arm/boot/dts/tegra30-peripherals-opp.dtsi | 1412 +++++++++++++++++
arch/arm/boot/dts/tegra30.dtsi | 153 ++
7 files changed, 1583 insertions(+), 3 deletions(-)

diff --git a/arch/arm/boot/dts/tegra30-asus-nexus7-grouper-common.dtsi b/arch/arm/boot/dts/tegra30-asus-nexus7-grouper-common.dtsi
index 9732cd6f20b7..4f116c26f6ce 100644
--- a/arch/arm/boot/dts/tegra30-asus-nexus7-grouper-common.dtsi
+++ b/arch/arm/boot/dts/tegra30-asus-nexus7-grouper-common.dtsi
@@ -957,6 +957,7 @@ pmc@7000e400 {
nvidia,core-pwr-off-time = <0>;
nvidia,core-power-req-active-high;
nvidia,sys-clock-req-active-high;
+ core-supply = <&vdd_core>;
};

ahub@70080000 {
diff --git a/arch/arm/boot/dts/tegra30-beaver.dts b/arch/arm/boot/dts/tegra30-beaver.dts
index e159feeedef7..b54cbb24c4d3 100644
--- a/arch/arm/boot/dts/tegra30-beaver.dts
+++ b/arch/arm/boot/dts/tegra30-beaver.dts
@@ -1915,6 +1915,7 @@ pmc@7000e400 {
nvidia,core-pwr-off-time = <0>;
nvidia,core-power-req-active-high;
nvidia,sys-clock-req-active-high;
+ core-supply = <&core_vdd_reg>;
};

ahub@70080000 {
diff --git a/arch/arm/boot/dts/tegra30-cardhu.dtsi b/arch/arm/boot/dts/tegra30-cardhu.dtsi
index 448f1397e64a..b2bba923eb93 100644
--- a/arch/arm/boot/dts/tegra30-cardhu.dtsi
+++ b/arch/arm/boot/dts/tegra30-cardhu.dtsi
@@ -391,6 +391,7 @@ pmc@7000e400 {
nvidia,core-pwr-off-time = <0>;
nvidia,core-power-req-active-high;
nvidia,sys-clock-req-active-high;
+ core-supply = <&vdd_core>;
};

ahub@70080000 {
diff --git a/arch/arm/boot/dts/tegra30-colibri.dtsi b/arch/arm/boot/dts/tegra30-colibri.dtsi
index 413e35215804..0627b64f044d 100644
--- a/arch/arm/boot/dts/tegra30-colibri.dtsi
+++ b/arch/arm/boot/dts/tegra30-colibri.dtsi
@@ -765,9 +765,14 @@ vdd1_reg: vdd1 {

vddctrl_reg: vddctrl {
regulator-name = "+V1.0_VDD_CPU";
- regulator-min-microvolt = <1150000>;
- regulator-max-microvolt = <1150000>;
+ regulator-min-microvolt = <800000>;
+ regulator-max-microvolt = <1250000>;
+ regulator-coupled-with = <&vdd_core>;
+ regulator-coupled-max-spread = <300000>;
+ regulator-max-step-microvolt = <100000>;
regulator-always-on;
+
+ nvidia,tegra-cpu-regulator;
};

reg_1v8_vio: vio {
@@ -890,18 +895,23 @@ temp-sensor@4c {
};

/* SW: +V1.2_VDD_CORE */
- regulator@60 {
+ vdd_core: regulator@60 {
compatible = "ti,tps62362";
reg = <0x60>;

regulator-name = "tps62362-vout";
regulator-min-microvolt = <900000>;
regulator-max-microvolt = <1400000>;
+ regulator-coupled-with = <&vddctrl_reg>;
+ regulator-coupled-max-spread = <300000>;
+ regulator-max-step-microvolt = <100000>;
regulator-boot-on;
regulator-always-on;
ti,vsel0-state-low;
/* VSEL1: EN_CORE_DVFS_N low for DVFS */
ti,vsel1-state-low;
+
+ nvidia,tegra-core-regulator;
};
};

@@ -914,6 +924,7 @@ pmc@7000e400 {
nvidia,core-pwr-off-time = <0>;
nvidia,core-power-req-active-high;
nvidia,sys-clock-req-active-high;
+ core-supply = <&vdd_core>;

/* Set DEV_OFF bit in DCDC control register of TPS65911 PMIC */
i2c-thermtrip {
diff --git a/arch/arm/boot/dts/tegra30-ouya.dts b/arch/arm/boot/dts/tegra30-ouya.dts
index 90db5ff72537..2a79cd4662b1 100644
--- a/arch/arm/boot/dts/tegra30-ouya.dts
+++ b/arch/arm/boot/dts/tegra30-ouya.dts
@@ -274,6 +274,7 @@ pmc@7000e400 {
nvidia,core-pwr-off-time = <458>;
nvidia,core-power-req-active-high;
nvidia,sys-clock-req-active-high;
+ core-supply = <&vdd_core>;
};

mc_timings: memory-controller@7000f000 {
diff --git a/arch/arm/boot/dts/tegra30-peripherals-opp.dtsi b/arch/arm/boot/dts/tegra30-peripherals-opp.dtsi
index 2c9780319725..e77f0be7ade3 100644
--- a/arch/arm/boot/dts/tegra30-peripherals-opp.dtsi
+++ b/arch/arm/boot/dts/tegra30-peripherals-opp.dtsi
@@ -1,6 +1,56 @@
// SPDX-License-Identifier: GPL-2.0

/ {
+ core_opp_table: core-power-domain-opp-table {
+ compatible = "operating-points-v2";
+ opp-shared;
+
+ core_opp_950: opp@950000 {
+ opp-microvolt = <950000 950000 1350000>;
+ opp-level = <950000>;
+ };
+
+ core_opp_1000: opp@1000000 {
+ opp-microvolt = <1000000 1000000 1350000>;
+ opp-level = <1000000>;
+ };
+
+ core_opp_1050: opp@1050000 {
+ opp-microvolt = <1050000 1050000 1350000>;
+ opp-level = <1050000>;
+ };
+
+ core_opp_1100: opp@1100000 {
+ opp-microvolt = <1100000 1100000 1350000>;
+ opp-level = <1100000>;
+ };
+
+ core_opp_1150: opp@1150000 {
+ opp-microvolt = <1150000 1150000 1350000>;
+ opp-level = <1150000>;
+ };
+
+ core_opp_1200: opp@1200000 {
+ opp-microvolt = <1200000 1200000 1350000>;
+ opp-level = <1200000>;
+ };
+
+ core_opp_1250: opp@1250000 {
+ opp-microvolt = <1250000 1250000 1350000>;
+ opp-level = <1250000>;
+ };
+
+ core_opp_1300: opp@1300000 {
+ opp-microvolt = <1300000 1300000 1350000>;
+ opp-level = <1300000>;
+ };
+
+ core_opp_1350: opp@1350000 {
+ opp-microvolt = <1350000 1350000 1350000>;
+ opp-level = <1350000>;
+ };
+ };
+
emc_icc_dvfs_opp_table: emc-dvfs-opp-table {
compatible = "operating-points-v2";

@@ -8,126 +58,147 @@ opp@12750000,950 {
opp-microvolt = <950000 950000 1350000>;
opp-hz = /bits/ 64 <12750000>;
opp-supported-hw = <0x0006>;
+ required-opps = <&core_opp_950>;
};

opp@12750000,1000 {
opp-microvolt = <1000000 1000000 1350000>;
opp-hz = /bits/ 64 <12750000>;
opp-supported-hw = <0x0001>;
+ required-opps = <&core_opp_1000>;
};

opp@12750000,1250 {
opp-microvolt = <1250000 1250000 1350000>;
opp-hz = /bits/ 64 <12750000>;
opp-supported-hw = <0x0008>;
+ required-opps = <&core_opp_1250>;
};

opp@25500000,950 {
opp-microvolt = <950000 950000 1350000>;
opp-hz = /bits/ 64 <25500000>;
opp-supported-hw = <0x0006>;
+ required-opps = <&core_opp_950>;
};

opp@25500000,1000 {
opp-microvolt = <1000000 1000000 1350000>;
opp-hz = /bits/ 64 <25500000>;
opp-supported-hw = <0x0001>;
+ required-opps = <&core_opp_1000>;
};

opp@25500000,1250 {
opp-microvolt = <1250000 1250000 1350000>;
opp-hz = /bits/ 64 <25500000>;
opp-supported-hw = <0x0008>;
+ required-opps = <&core_opp_1250>;
};

opp@27000000,950 {
opp-microvolt = <950000 950000 1350000>;
opp-hz = /bits/ 64 <27000000>;
opp-supported-hw = <0x0006>;
+ required-opps = <&core_opp_950>;
};

opp@27000000,1000 {
opp-microvolt = <1000000 1000000 1350000>;
opp-hz = /bits/ 64 <27000000>;
opp-supported-hw = <0x0001>;
+ required-opps = <&core_opp_1000>;
};

opp@27000000,1250 {
opp-microvolt = <1250000 1250000 1350000>;
opp-hz = /bits/ 64 <27000000>;
opp-supported-hw = <0x0008>;
+ required-opps = <&core_opp_1250>;
};

opp@51000000,950 {
opp-microvolt = <950000 950000 1350000>;
opp-hz = /bits/ 64 <51000000>;
opp-supported-hw = <0x0006>;
+ required-opps = <&core_opp_950>;
};

opp@51000000,1000 {
opp-microvolt = <1000000 1000000 1350000>;
opp-hz = /bits/ 64 <51000000>;
opp-supported-hw = <0x0001>;
+ required-opps = <&core_opp_1000>;
};

opp@51000000,1250 {
opp-microvolt = <1250000 1250000 1350000>;
opp-hz = /bits/ 64 <51000000>;
opp-supported-hw = <0x0008>;
+ required-opps = <&core_opp_1250>;
};

opp@54000000,950 {
opp-microvolt = <950000 950000 1350000>;
opp-hz = /bits/ 64 <54000000>;
opp-supported-hw = <0x0006>;
+ required-opps = <&core_opp_950>;
};

opp@54000000,1000 {
opp-microvolt = <1000000 1000000 1350000>;
opp-hz = /bits/ 64 <54000000>;
opp-supported-hw = <0x0001>;
+ required-opps = <&core_opp_1000>;
};

opp@54000000,1250 {
opp-microvolt = <1250000 1250000 1350000>;
opp-hz = /bits/ 64 <54000000>;
opp-supported-hw = <0x0008>;
+ required-opps = <&core_opp_1250>;
};

opp@102000000,950 {
opp-microvolt = <950000 950000 1350000>;
opp-hz = /bits/ 64 <102000000>;
opp-supported-hw = <0x0006>;
+ required-opps = <&core_opp_950>;
};

opp@102000000,1000 {
opp-microvolt = <1000000 1000000 1350000>;
opp-hz = /bits/ 64 <102000000>;
opp-supported-hw = <0x0001>;
+ required-opps = <&core_opp_1000>;
};

opp@102000000,1250 {
opp-microvolt = <1250000 1250000 1350000>;
opp-hz = /bits/ 64 <102000000>;
opp-supported-hw = <0x0008>;
+ required-opps = <&core_opp_1250>;
};

opp@108000000,1000 {
opp-microvolt = <1000000 1000000 1350000>;
opp-hz = /bits/ 64 <108000000>;
opp-supported-hw = <0x0007>;
+ required-opps = <&core_opp_1000>;
};

opp@108000000,1250 {
opp-microvolt = <1250000 1250000 1350000>;
opp-hz = /bits/ 64 <108000000>;
opp-supported-hw = <0x0008>;
+ required-opps = <&core_opp_1250>;
};

opp@204000000,1000 {
opp-microvolt = <1000000 1000000 1350000>;
opp-hz = /bits/ 64 <204000000>;
opp-supported-hw = <0x0007>;
+ required-opps = <&core_opp_1000>;
opp-suspend;
};

@@ -135,6 +206,7 @@ opp@204000000,1250 {
opp-microvolt = <1250000 1250000 1350000>;
opp-hz = /bits/ 64 <204000000>;
opp-supported-hw = <0x0008>;
+ required-opps = <&core_opp_1250>;
opp-suspend;
};

@@ -142,126 +214,147 @@ opp@333500000,1000 {
opp-microvolt = <1000000 1000000 1350000>;
opp-hz = /bits/ 64 <333500000>;
opp-supported-hw = <0x0006>;
+ required-opps = <&core_opp_1000>;
};

opp@333500000,1200 {
opp-microvolt = <1200000 1200000 1350000>;
opp-hz = /bits/ 64 <333500000>;
opp-supported-hw = <0x0001>;
+ required-opps = <&core_opp_1200>;
};

opp@333500000,1250 {
opp-microvolt = <1250000 1250000 1350000>;
opp-hz = /bits/ 64 <333500000>;
opp-supported-hw = <0x0008>;
+ required-opps = <&core_opp_1250>;
};

opp@375000000,1000 {
opp-microvolt = <1000000 1000000 1350000>;
opp-hz = /bits/ 64 <375000000>;
opp-supported-hw = <0x0006>;
+ required-opps = <&core_opp_1000>;
};

opp@375000000,1200 {
opp-microvolt = <1200000 1200000 1350000>;
opp-hz = /bits/ 64 <375000000>;
opp-supported-hw = <0x0001>;
+ required-opps = <&core_opp_1200>;
};

opp@375000000,1250 {
opp-microvolt = <1250000 1250000 1350000>;
opp-hz = /bits/ 64 <375000000>;
opp-supported-hw = <0x0008>;
+ required-opps = <&core_opp_1250>;
};

opp@400000000,1000 {
opp-microvolt = <1000000 1000000 1350000>;
opp-hz = /bits/ 64 <400000000>;
opp-supported-hw = <0x0006>;
+ required-opps = <&core_opp_1000>;
};

opp@400000000,1200 {
opp-microvolt = <1200000 1200000 1350000>;
opp-hz = /bits/ 64 <400000000>;
opp-supported-hw = <0x0001>;
+ required-opps = <&core_opp_1200>;
};

opp@400000000,1250 {
opp-microvolt = <1250000 1250000 1350000>;
opp-hz = /bits/ 64 <400000000>;
opp-supported-hw = <0x0008>;
+ required-opps = <&core_opp_1250>;
};

opp@416000000,1200 {
opp-microvolt = <1200000 1200000 1350000>;
opp-hz = /bits/ 64 <416000000>;
opp-supported-hw = <0x0007>;
+ required-opps = <&core_opp_1200>;
};

opp@416000000,1250 {
opp-microvolt = <1250000 1250000 1350000>;
opp-hz = /bits/ 64 <416000000>;
opp-supported-hw = <0x0008>;
+ required-opps = <&core_opp_1250>;
};

opp@450000000,1200 {
opp-microvolt = <1200000 1200000 1350000>;
opp-hz = /bits/ 64 <450000000>;
opp-supported-hw = <0x0007>;
+ required-opps = <&core_opp_1200>;
};

opp@450000000,1250 {
opp-microvolt = <1250000 1250000 1350000>;
opp-hz = /bits/ 64 <450000000>;
opp-supported-hw = <0x0008>;
+ required-opps = <&core_opp_1250>;
};

opp@533000000,1200 {
opp-microvolt = <1200000 1200000 1350000>;
opp-hz = /bits/ 64 <533000000>;
opp-supported-hw = <0x0007>;
+ required-opps = <&core_opp_1200>;
};

opp@533000000,1250 {
opp-microvolt = <1250000 1250000 1350000>;
opp-hz = /bits/ 64 <533000000>;
opp-supported-hw = <0x0008>;
+ required-opps = <&core_opp_1250>;
};

opp@625000000,1200 {
opp-microvolt = <1200000 1200000 1350000>;
opp-hz = /bits/ 64 <625000000>;
opp-supported-hw = <0x0006>;
+ required-opps = <&core_opp_1200>;
};

opp@625000000,1250 {
opp-microvolt = <1250000 1250000 1350000>;
opp-hz = /bits/ 64 <625000000>;
opp-supported-hw = <0x0008>;
+ required-opps = <&core_opp_1250>;
};

opp@667000000,1200 {
opp-microvolt = <1200000 1200000 1350000>;
opp-hz = /bits/ 64 <667000000>;
opp-supported-hw = <0x0006>;
+ required-opps = <&core_opp_1200>;
};

opp@750000000,1300 {
opp-microvolt = <1300000 1300000 1350000>;
opp-hz = /bits/ 64 <750000000>;
opp-supported-hw = <0x0004>;
+ required-opps = <&core_opp_1300>;
};

opp@800000000,1300 {
opp-microvolt = <1300000 1300000 1350000>;
opp-hz = /bits/ 64 <800000000>;
opp-supported-hw = <0x0004>;
+ required-opps = <&core_opp_1300>;
};

opp@900000000,1350 {
opp-microvolt = <1350000 1350000 1350000>;
opp-hz = /bits/ 64 <900000000>;
opp-supported-hw = <0x0004>;
+ required-opps = <&core_opp_1350>;
};
};

@@ -383,4 +476,1323 @@ opp@900000000 {
opp-peak-kBps = <7200000>;
};
};
+
+ gr2d_dvfs_opp_table: gr2d-opp-table {
+ compatible = "operating-points-v2";
+
+ opp@267000000,1000 {
+ opp-microvolt = <1000000 1000000 1350000>;
+ opp-hz = /bits/ 64 <267000000>;
+ opp-supported-hw = <0x0007>;
+ required-opps = <&core_opp_1000>;
+ };
+
+ opp@285000000,1050 {
+ opp-microvolt = <1050000 1050000 1350000>;
+ opp-hz = /bits/ 64 <285000000>;
+ opp-supported-hw = <0x0003>;
+ required-opps = <&core_opp_1050>;
+ };
+
+ opp@304000000,1050 {
+ opp-microvolt = <1050000 1050000 1350000>;
+ opp-hz = /bits/ 64 <304000000>;
+ opp-supported-hw = <0x0004>;
+ required-opps = <&core_opp_1050>;
+ };
+
+ opp@332000000,1100 {
+ opp-microvolt = <1100000 1100000 1350000>;
+ opp-hz = /bits/ 64 <332000000>;
+ opp-supported-hw = <0x0003>;
+ required-opps = <&core_opp_1100>;
+ };
+
+ opp@361000000,1100 {
+ opp-microvolt = <1100000 1100000 1350000>;
+ opp-hz = /bits/ 64 <361000000>;
+ opp-supported-hw = <0x0004>;
+ required-opps = <&core_opp_1100>;
+ };
+
+ opp@380000000,1150 {
+ opp-microvolt = <1150000 1150000 1350000>;
+ opp-hz = /bits/ 64 <380000000>;
+ opp-supported-hw = <0x0003>;
+ required-opps = <&core_opp_1150>;
+ };
+
+ opp@408000000,1150 {
+ opp-microvolt = <1150000 1150000 1350000>;
+ opp-hz = /bits/ 64 <408000000>;
+ opp-supported-hw = <0x0004>;
+ required-opps = <&core_opp_1150>;
+ };
+
+ opp@416000000,1200 {
+ opp-microvolt = <1200000 1200000 1350000>;
+ opp-hz = /bits/ 64 <416000000>;
+ opp-supported-hw = <0x0003>;
+ required-opps = <&core_opp_1200>;
+ };
+
+ opp@446000000,1200 {
+ opp-microvolt = <1200000 1200000 1350000>;
+ opp-hz = /bits/ 64 <446000000>;
+ opp-supported-hw = <0x0004>;
+ required-opps = <&core_opp_1200>;
+ };
+
+ opp@484000000,1250 {
+ opp-microvolt = <1250000 1250000 1350000>;
+ opp-hz = /bits/ 64 <484000000>;
+ opp-supported-hw = <0x000C>;
+ required-opps = <&core_opp_1250>;
+ };
+
+ opp@520000000,1300 {
+ opp-microvolt = <1300000 1300000 1350000>;
+ opp-hz = /bits/ 64 <520000000>;
+ opp-supported-hw = <0x0004>;
+ required-opps = <&core_opp_1300>;
+ };
+
+ opp@600000000,1350 {
+ opp-microvolt = <1350000 1350000 1350000>;
+ opp-hz = /bits/ 64 <600000000>;
+ opp-supported-hw = <0x0004>;
+ required-opps = <&core_opp_1350>;
+ };
+ };
+
+ gr3d_dvfs_opp_table: gr3d-opp-table {
+ compatible = "operating-points-v2";
+
+ opp@234000000,1000 {
+ opp-microvolt = <1000000 1000000 1350000>;
+ opp-hz = /bits/ 64 <234000000>;
+ opp-supported-hw = <0x0003>;
+ required-opps = <&core_opp_1000>, <&core_opp_1000>;
+ };
+
+ opp@247000000,1000 {
+ opp-microvolt = <1000000 1000000 1350000>;
+ opp-hz = /bits/ 64 <247000000>;
+ opp-supported-hw = <0x0004>;
+ required-opps = <&core_opp_1000>, <&core_opp_1000>;
+ };
+
+ opp@285000000,1050 {
+ opp-microvolt = <1050000 1050000 1350000>;
+ opp-hz = /bits/ 64 <285000000>;
+ opp-supported-hw = <0x0003>;
+ required-opps = <&core_opp_1050>, <&core_opp_1050>;
+ };
+
+ opp@304000000,1050 {
+ opp-microvolt = <1050000 1050000 1350000>;
+ opp-hz = /bits/ 64 <304000000>;
+ opp-supported-hw = <0x0004>;
+ required-opps = <&core_opp_1050>, <&core_opp_1050>;
+ };
+
+ opp@332000000,1100 {
+ opp-microvolt = <1100000 1100000 1350000>;
+ opp-hz = /bits/ 64 <332000000>;
+ opp-supported-hw = <0x0003>;
+ required-opps = <&core_opp_1100>, <&core_opp_1100>;
+ };
+
+ opp@361000000,1100 {
+ opp-microvolt = <1100000 1100000 1350000>;
+ opp-hz = /bits/ 64 <361000000>;
+ opp-supported-hw = <0x0004>;
+ required-opps = <&core_opp_1100>, <&core_opp_1100>;
+ };
+
+ opp@380000000,1150 {
+ opp-microvolt = <1150000 1150000 1350000>;
+ opp-hz = /bits/ 64 <380000000>;
+ opp-supported-hw = <0x0003>;
+ required-opps = <&core_opp_1150>, <&core_opp_1150>;
+ };
+
+ opp@408000000,1150 {
+ opp-microvolt = <1150000 1150000 1350000>;
+ opp-hz = /bits/ 64 <408000000>;
+ opp-supported-hw = <0x0004>;
+ required-opps = <&core_opp_1150>, <&core_opp_1150>;
+ };
+
+ opp@416000000,1200 {
+ opp-microvolt = <1200000 1200000 1350000>;
+ opp-hz = /bits/ 64 <416000000>;
+ opp-supported-hw = <0x0003>;
+ required-opps = <&core_opp_1200>, <&core_opp_1200>;
+ };
+
+ opp@446000000,1200 {
+ opp-microvolt = <1200000 1200000 1350000>;
+ opp-hz = /bits/ 64 <446000000>;
+ opp-supported-hw = <0x0004>;
+ required-opps = <&core_opp_1200>, <&core_opp_1200>;
+ };
+
+ opp@484000000,1250 {
+ opp-microvolt = <1250000 1250000 1350000>;
+ opp-hz = /bits/ 64 <484000000>;
+ opp-supported-hw = <0x000C>;
+ required-opps = <&core_opp_1250>, <&core_opp_1250>;
+ };
+
+ opp@520000000,1300 {
+ opp-microvolt = <1300000 1300000 1350000>;
+ opp-hz = /bits/ 64 <520000000>;
+ opp-supported-hw = <0x0004>;
+ required-opps = <&core_opp_1300>, <&core_opp_1300>;
+ };
+
+ opp@600000000,1350 {
+ opp-microvolt = <1350000 1350000 1350000>;
+ opp-hz = /bits/ 64 <600000000>;
+ opp-supported-hw = <0x0004>;
+ required-opps = <&core_opp_1350>, <&core_opp_1350>;
+ };
+ };
+
+ afi_dvfs_opp_table: afi-opp-table {
+ compatible = "operating-points-v2";
+
+ opp@250000000,1000 {
+ opp-microvolt = <1000000 1000000 1350000>;
+ opp-hz = /bits/ 64 <250000000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_1000>;
+ };
+ };
+
+ cve_dvfs_opp_table: cve-opp-table {
+ compatible = "operating-points-v2";
+
+ opp@297000000,1050 {
+ opp-microvolt = <1050000 1050000 1350000>;
+ opp-hz = /bits/ 64 <297000000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_1050>;
+ };
+ };
+
+ disp1_dvfs_opp_table: disp1-opp-table {
+ compatible = "operating-points-v2";
+
+ opp@120000000,1000 {
+ opp-microvolt = <1000000 1000000 1350000>;
+ opp-hz = /bits/ 64 <120000000>;
+ opp-supported-hw = <0x0009>;
+ required-opps = <&core_opp_1000>;
+ };
+
+ opp@155000000,1000 {
+ opp-microvolt = <1000000 1000000 1350000>;
+ opp-hz = /bits/ 64 <155000000>;
+ opp-supported-hw = <0x0006>;
+ required-opps = <&core_opp_1000>;
+ };
+
+ opp@190000000,1200 {
+ opp-microvolt = <1200000 1200000 1350000>;
+ opp-hz = /bits/ 64 <190000000>;
+ opp-supported-hw = <0x0009>;
+ required-opps = <&core_opp_1200>;
+ };
+
+ opp@268000000,1050 {
+ opp-microvolt = <1050000 1050000 1350000>;
+ opp-hz = /bits/ 64 <268000000>;
+ opp-supported-hw = <0x0006>;
+ required-opps = <&core_opp_1050>;
+ };
+ };
+
+ disp2_dvfs_opp_table: disp2-opp-table {
+ compatible = "operating-points-v2";
+
+ opp@120000000,1000 {
+ opp-microvolt = <1000000 1000000 1350000>;
+ opp-hz = /bits/ 64 <120000000>;
+ opp-supported-hw = <0x0009>;
+ required-opps = <&core_opp_1000>;
+ };
+
+ opp@155000000,1000 {
+ opp-microvolt = <1000000 1000000 1350000>;
+ opp-hz = /bits/ 64 <155000000>;
+ opp-supported-hw = <0x0006>;
+ required-opps = <&core_opp_1000>;
+ };
+
+ opp@190000000,1200 {
+ opp-microvolt = <1200000 1200000 1350000>;
+ opp-hz = /bits/ 64 <190000000>;
+ opp-supported-hw = <0x0009>;
+ required-opps = <&core_opp_1200>;
+ };
+
+ opp@268000000,1050 {
+ opp-microvolt = <1050000 1050000 1350000>;
+ opp-hz = /bits/ 64 <268000000>;
+ opp-supported-hw = <0x0006>;
+ required-opps = <&core_opp_1050>;
+ };
+ };
+
+ dsia_dvfs_opp_table: dsia-opp-table {
+ compatible = "operating-points-v2";
+
+ opp@275000000,1000 {
+ opp-microvolt = <1000000 1000000 1350000>;
+ opp-hz = /bits/ 64 <275000000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_1000>;
+ };
+ };
+
+ dsib_dvfs_opp_table: dsib-opp-table {
+ compatible = "operating-points-v2";
+
+ opp@275000000,1000 {
+ opp-microvolt = <1000000 1000000 1350000>;
+ opp-hz = /bits/ 64 <275000000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_1000>;
+ };
+ };
+
+ epp_dvfs_opp_table: epp-opp-table {
+ compatible = "operating-points-v2";
+
+ opp@267000000,1000 {
+ opp-microvolt = <1000000 1000000 1350000>;
+ opp-hz = /bits/ 64 <267000000>;
+ opp-supported-hw = <0x0007>;
+ required-opps = <&core_opp_1000>;
+ };
+
+ opp@285000000,1050 {
+ opp-microvolt = <1050000 1050000 1350000>;
+ opp-hz = /bits/ 64 <285000000>;
+ opp-supported-hw = <0x0003>;
+ required-opps = <&core_opp_1050>;
+ };
+
+ opp@304000000,1050 {
+ opp-microvolt = <1050000 1050000 1350000>;
+ opp-hz = /bits/ 64 <304000000>;
+ opp-supported-hw = <0x0004>;
+ required-opps = <&core_opp_1050>;
+ };
+
+ opp@332000000,1100 {
+ opp-microvolt = <1100000 1100000 1350000>;
+ opp-hz = /bits/ 64 <332000000>;
+ opp-supported-hw = <0x0003>;
+ required-opps = <&core_opp_1100>;
+ };
+
+ opp@361000000,1100 {
+ opp-microvolt = <1100000 1100000 1350000>;
+ opp-hz = /bits/ 64 <361000000>;
+ opp-supported-hw = <0x0004>;
+ required-opps = <&core_opp_1100>;
+ };
+
+ opp@380000000,1150 {
+ opp-microvolt = <1150000 1150000 1350000>;
+ opp-hz = /bits/ 64 <380000000>;
+ opp-supported-hw = <0x0003>;
+ required-opps = <&core_opp_1150>;
+ };
+
+ opp@408000000,1150 {
+ opp-microvolt = <1150000 1150000 1350000>;
+ opp-hz = /bits/ 64 <408000000>;
+ opp-supported-hw = <0x0004>;
+ required-opps = <&core_opp_1150>;
+ };
+
+ opp@416000000,1200 {
+ opp-microvolt = <1200000 1200000 1350000>;
+ opp-hz = /bits/ 64 <416000000>;
+ opp-supported-hw = <0x0003>;
+ required-opps = <&core_opp_1200>;
+ };
+
+ opp@446000000,1200 {
+ opp-microvolt = <1200000 1200000 1350000>;
+ opp-hz = /bits/ 64 <446000000>;
+ opp-supported-hw = <0x0004>;
+ required-opps = <&core_opp_1200>;
+ };
+
+ opp@484000000,1250 {
+ opp-microvolt = <1250000 1250000 1350000>;
+ opp-hz = /bits/ 64 <484000000>;
+ opp-supported-hw = <0x000C>;
+ required-opps = <&core_opp_1250>;
+ };
+
+ opp@520000000,1300 {
+ opp-microvolt = <1300000 1300000 1350000>;
+ opp-hz = /bits/ 64 <520000000>;
+ opp-supported-hw = <0x0004>;
+ required-opps = <&core_opp_1300>;
+ };
+
+ opp@600000000,1350 {
+ opp-microvolt = <1350000 1350000 1350000>;
+ opp-hz = /bits/ 64 <600000000>;
+ opp-supported-hw = <0x0004>;
+ required-opps = <&core_opp_1350>;
+ };
+ };
+
+ fuse_burn_dvfs_opp_table: fuse-burn-opp-table {
+ compatible = "operating-points-v2";
+
+ opp@26000000,1150 {
+ opp-microvolt = <1150000 1150000 1350000>;
+ opp-hz = /bits/ 64 <26000000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_1150>;
+ };
+ };
+
+ hdmi_dvfs_opp_table: hdmi-opp-table {
+ compatible = "operating-points-v2";
+
+ opp@148500000,1000 {
+ opp-microvolt = <1000000 1000000 1350000>;
+ opp-hz = /bits/ 64 <148500000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_1000>;
+ };
+ };
+
+ host1x_dvfs_opp_table: host1x-opp-table {
+ compatible = "operating-points-v2";
+
+ opp@152000000,1000 {
+ opp-microvolt = <1000000 1000000 1350000>;
+ opp-hz = /bits/ 64 <152000000>;
+ opp-supported-hw = <0x0007>;
+ required-opps = <&core_opp_1000>;
+ };
+
+ opp@188000000,1050 {
+ opp-microvolt = <1050000 1050000 1350000>;
+ opp-hz = /bits/ 64 <188000000>;
+ opp-supported-hw = <0x0007>;
+ required-opps = <&core_opp_1050>;
+ };
+
+ opp@222000000,1100 {
+ opp-microvolt = <1100000 1100000 1350000>;
+ opp-hz = /bits/ 64 <222000000>;
+ opp-supported-hw = <0x0007>;
+ required-opps = <&core_opp_1100>;
+ };
+
+ opp@242000000,1250 {
+ opp-microvolt = <1250000 1250000 1350000>;
+ opp-hz = /bits/ 64 <242000000>;
+ opp-supported-hw = <0x0008>;
+ required-opps = <&core_opp_1250>;
+ };
+
+ opp@254000000,1150 {
+ opp-microvolt = <1150000 1150000 1350000>;
+ opp-hz = /bits/ 64 <254000000>;
+ opp-supported-hw = <0x0007>;
+ required-opps = <&core_opp_1150>;
+ };
+
+ opp@267000000,1200 {
+ opp-microvolt = <1200000 1200000 1350000>;
+ opp-hz = /bits/ 64 <267000000>;
+ opp-supported-hw = <0x0007>;
+ required-opps = <&core_opp_1200>;
+ };
+
+ opp@300000000,1350 {
+ opp-microvolt = <1350000 1350000 1350000>;
+ opp-hz = /bits/ 64 <300000000>;
+ opp-supported-hw = <0x0004>;
+ required-opps = <&core_opp_1350>;
+ };
+ };
+
+ mipi_dvfs_opp_table: mipi-opp-table {
+ compatible = "operating-points-v2";
+
+ opp@60000000,1200 {
+ opp-microvolt = <1200000 1200000 1350000>;
+ opp-hz = /bits/ 64 <60000000>;
+ opp-supported-hw = <0x0006>;
+ required-opps = <&core_opp_1200>;
+ };
+ };
+
+ mpe_dvfs_opp_table: mpe-opp-table {
+ compatible = "operating-points-v2";
+
+ opp@234000000,1000 {
+ opp-microvolt = <1000000 1000000 1350000>;
+ opp-hz = /bits/ 64 <234000000>;
+ opp-supported-hw = <0x0003>;
+ required-opps = <&core_opp_1000>;
+ };
+
+ opp@247000000,1000 {
+ opp-microvolt = <1000000 1000000 1350000>;
+ opp-hz = /bits/ 64 <247000000>;
+ opp-supported-hw = <0x0004>;
+ required-opps = <&core_opp_1000>;
+ };
+
+ opp@285000000,1050 {
+ opp-microvolt = <1050000 1050000 1350000>;
+ opp-hz = /bits/ 64 <285000000>;
+ opp-supported-hw = <0x0003>;
+ required-opps = <&core_opp_1050>;
+ };
+
+ opp@304000000,1050 {
+ opp-microvolt = <1050000 1050000 1350000>;
+ opp-hz = /bits/ 64 <304000000>;
+ opp-supported-hw = <0x0004>;
+ required-opps = <&core_opp_1050>;
+ };
+
+ opp@332000000,1100 {
+ opp-microvolt = <1100000 1100000 1350000>;
+ opp-hz = /bits/ 64 <332000000>;
+ opp-supported-hw = <0x0003>;
+ required-opps = <&core_opp_1100>;
+ };
+
+ opp@361000000,1100 {
+ opp-microvolt = <1100000 1100000 1350000>;
+ opp-hz = /bits/ 64 <361000000>;
+ opp-supported-hw = <0x0004>;
+ required-opps = <&core_opp_1100>;
+ };
+
+ opp@380000000,1150 {
+ opp-microvolt = <1150000 1150000 1350000>;
+ opp-hz = /bits/ 64 <380000000>;
+ opp-supported-hw = <0x0003>;
+ required-opps = <&core_opp_1150>;
+ };
+
+ opp@408000000,1150 {
+ opp-microvolt = <1150000 1150000 1350000>;
+ opp-hz = /bits/ 64 <408000000>;
+ opp-supported-hw = <0x0004>;
+ required-opps = <&core_opp_1150>;
+ };
+
+ opp@416000000,1200 {
+ opp-microvolt = <1200000 1200000 1350000>;
+ opp-hz = /bits/ 64 <416000000>;
+ opp-supported-hw = <0x0003>;
+ required-opps = <&core_opp_1200>;
+ };
+
+ opp@446000000,1200 {
+ opp-microvolt = <1200000 1200000 1350000>;
+ opp-hz = /bits/ 64 <446000000>;
+ opp-supported-hw = <0x0004>;
+ required-opps = <&core_opp_1200>;
+ };
+
+ opp@484000000,1250 {
+ opp-microvolt = <1250000 1250000 1350000>;
+ opp-hz = /bits/ 64 <484000000>;
+ opp-supported-hw = <0x000C>;
+ required-opps = <&core_opp_1250>;
+ };
+
+ opp@520000000,1300 {
+ opp-microvolt = <1300000 1300000 1350000>;
+ opp-hz = /bits/ 64 <520000000>;
+ opp-supported-hw = <0x0004>;
+ required-opps = <&core_opp_1300>;
+ };
+
+ opp@600000000,1350 {
+ opp-microvolt = <1350000 1350000 1350000>;
+ opp-hz = /bits/ 64 <600000000>;
+ opp-supported-hw = <0x0004>;
+ required-opps = <&core_opp_1350>;
+ };
+ };
+
+ sclk_dvfs_opp_table: sclk-opp-table {
+ compatible = "operating-points-v2";
+
+ opp@51000000,950 {
+ opp-microvolt = <950000 950000 1350000>;
+ opp-hz = /bits/ 64 <51000000>;
+ opp-supported-hw = <0x0006>;
+ required-opps = <&core_opp_950>;
+ };
+
+ opp@136000000,1000 {
+ opp-microvolt = <1000000 1000000 1350000>;
+ opp-hz = /bits/ 64 <136000000>;
+ opp-supported-hw = <0x0001>;
+ required-opps = <&core_opp_1000>;
+ };
+
+ opp@164000000,1050 {
+ opp-microvolt = <1050000 1050000 1350000>;
+ opp-hz = /bits/ 64 <164000000>;
+ opp-supported-hw = <0x0001>;
+ required-opps = <&core_opp_1050>;
+ };
+
+ opp@191000000,1100 {
+ opp-microvolt = <1100000 1100000 1350000>;
+ opp-hz = /bits/ 64 <191000000>;
+ opp-supported-hw = <0x0001>;
+ required-opps = <&core_opp_1100>;
+ };
+
+ opp@205000000,1000 {
+ opp-microvolt = <1000000 1000000 1350000>;
+ opp-hz = /bits/ 64 <205000000>;
+ opp-supported-hw = <0x0006>;
+ required-opps = <&core_opp_1000>;
+ };
+
+ opp@216000000,1150 {
+ opp-microvolt = <1150000 1150000 1350000>;
+ opp-hz = /bits/ 64 <216000000>;
+ opp-supported-hw = <0x0001>;
+ required-opps = <&core_opp_1150>;
+ };
+
+ opp@227000000,1100 {
+ opp-microvolt = <1100000 1100000 1350000>;
+ opp-hz = /bits/ 64 <227000000>;
+ opp-supported-hw = <0x0006>;
+ required-opps = <&core_opp_1100>;
+ };
+
+ opp@267000000,1200 {
+ opp-microvolt = <1200000 1200000 1350000>;
+ opp-hz = /bits/ 64 <267000000>;
+ opp-supported-hw = <0x0006>;
+ required-opps = <&core_opp_1200>;
+ };
+
+ opp@334000000,1250 {
+ opp-microvolt = <1250000 1250000 1350000>;
+ opp-hz = /bits/ 64 <334000000>;
+ opp-supported-hw = <0x0004>;
+ required-opps = <&core_opp_1250>;
+ };
+
+ opp@378000000,1250 {
+ opp-microvolt = <1250000 1250000 1350000>;
+ opp-hz = /bits/ 64 <378000000>;
+ opp-supported-hw = <0x0008>;
+ required-opps = <&core_opp_1250>;
+ };
+ };
+
+ se_dvfs_opp_table: se-opp-table {
+ compatible = "operating-points-v2";
+
+ opp@267000000,1000 {
+ opp-microvolt = <1000000 1000000 1350000>;
+ opp-hz = /bits/ 64 <267000000>;
+ opp-supported-hw = <0x0007>;
+ required-opps = <&core_opp_1000>;
+ };
+
+ opp@285000000,1050 {
+ opp-microvolt = <1050000 1050000 1350000>;
+ opp-hz = /bits/ 64 <285000000>;
+ opp-supported-hw = <0x0003>;
+ required-opps = <&core_opp_1050>;
+ };
+
+ opp@304000000,1050 {
+ opp-microvolt = <1050000 1050000 1350000>;
+ opp-hz = /bits/ 64 <304000000>;
+ opp-supported-hw = <0x0004>;
+ required-opps = <&core_opp_1050>;
+ };
+
+ opp@332000000,1100 {
+ opp-microvolt = <1100000 1100000 1350000>;
+ opp-hz = /bits/ 64 <332000000>;
+ opp-supported-hw = <0x0003>;
+ required-opps = <&core_opp_1100>;
+ };
+
+ opp@361000000,1100 {
+ opp-microvolt = <1100000 1100000 1350000>;
+ opp-hz = /bits/ 64 <361000000>;
+ opp-supported-hw = <0x0004>;
+ required-opps = <&core_opp_1100>;
+ };
+
+ opp@380000000,1150 {
+ opp-microvolt = <1150000 1150000 1350000>;
+ opp-hz = /bits/ 64 <380000000>;
+ opp-supported-hw = <0x0003>;
+ required-opps = <&core_opp_1150>;
+ };
+
+ opp@408000000,1150 {
+ opp-microvolt = <1150000 1150000 1350000>;
+ opp-hz = /bits/ 64 <408000000>;
+ opp-supported-hw = <0x0004>;
+ required-opps = <&core_opp_1150>;
+ };
+
+ opp@416000000,1200 {
+ opp-microvolt = <1200000 1200000 1350000>;
+ opp-hz = /bits/ 64 <416000000>;
+ opp-supported-hw = <0x0003>;
+ required-opps = <&core_opp_1200>;
+ };
+
+ opp@446000000,1200 {
+ opp-microvolt = <1200000 1200000 1350000>;
+ opp-hz = /bits/ 64 <446000000>;
+ opp-supported-hw = <0x0004>;
+ required-opps = <&core_opp_1200>;
+ };
+
+ opp@484000000,1250 {
+ opp-microvolt = <1250000 1250000 1350000>;
+ opp-hz = /bits/ 64 <484000000>;
+ opp-supported-hw = <0x0004>;
+ required-opps = <&core_opp_1250>;
+ };
+
+ opp@520000000,1300 {
+ opp-microvolt = <1300000 1300000 1350000>;
+ opp-hz = /bits/ 64 <520000000>;
+ opp-supported-hw = <0x0004>;
+ required-opps = <&core_opp_1300>;
+ };
+
+ opp@600000000,1350 {
+ opp-microvolt = <1350000 1350000 1350000>;
+ opp-hz = /bits/ 64 <600000000>;
+ opp-supported-hw = <0x0004>;
+ required-opps = <&core_opp_1350>;
+ };
+
+ opp@625000000,1250 {
+ opp-microvolt = <1250000 1250000 1350000>;
+ opp-hz = /bits/ 64 <625000000>;
+ opp-supported-hw = <0x0008>;
+ required-opps = <&core_opp_1250>;
+ };
+ };
+
+ ndflash_dvfs_opp_table: ndflash-opp-table {
+ compatible = "operating-points-v2";
+
+ opp@120000000,1000 {
+ opp-microvolt = <1000000 1000000 1350000>;
+ opp-hz = /bits/ 64 <120000000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_1000>;
+ };
+
+ opp@200000000,1150 {
+ opp-microvolt = <1150000 1150000 1350000>;
+ opp-hz = /bits/ 64 <200000000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_1150>;
+ };
+ };
+
+ nor_dvfs_opp_table: nor-opp-table {
+ compatible = "operating-points-v2";
+
+ opp@108000000,1250 {
+ opp-microvolt = <1250000 1250000 1350000>;
+ opp-hz = /bits/ 64 <108000000>;
+ opp-supported-hw = <0x0008>;
+ required-opps = <&core_opp_1250>;
+ };
+
+ opp@115000000,1000 {
+ opp-microvolt = <1000000 1000000 1350000>;
+ opp-hz = /bits/ 64 <115000000>;
+ opp-supported-hw = <0x0007>;
+ required-opps = <&core_opp_1000>;
+ };
+
+ opp@130000000,1050 {
+ opp-microvolt = <1050000 1050000 1350000>;
+ opp-hz = /bits/ 64 <130000000>;
+ opp-supported-hw = <0x0007>;
+ required-opps = <&core_opp_1050>;
+ };
+
+ opp@133000000,1150 {
+ opp-microvolt = <1150000 1150000 1350000>;
+ opp-hz = /bits/ 64 <133000000>;
+ opp-supported-hw = <0x0007>;
+ required-opps = <&core_opp_1150>;
+ };
+ };
+
+ pcie_dvfs_opp_table: pcie-opp-table {
+ compatible = "operating-points-v2";
+
+ opp@250000000,1000 {
+ opp-microvolt = <1000000 1000000 1350000>;
+ opp-hz = /bits/ 64 <250000000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_1000>;
+ };
+ };
+
+ pll_c_dvfs_opp_table: pllc-opp-table {
+ compatible = "operating-points-v2";
+
+ opp@533000000,950 {
+ opp-microvolt = <950000 950000 1350000>;
+ opp-hz = /bits/ 64 <533000000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_950>;
+ };
+
+ opp@667000000,1000 {
+ opp-microvolt = <1000000 1000000 1350000>;
+ opp-hz = /bits/ 64 <667000000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_1000>;
+ };
+
+ opp@800000000,1100 {
+ opp-microvolt = <1100000 1100000 1350000>;
+ opp-hz = /bits/ 64 <800000000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_1100>;
+ };
+
+ opp@1066000000,1200 {
+ opp-microvolt = <1200000 1200000 1350000>;
+ opp-hz = /bits/ 64 <1066000000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_1200>;
+ };
+
+ opp@1200000000,1350 {
+ opp-microvolt = <1350000 1350000 1350000>;
+ opp-hz = /bits/ 64 <1200000000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_1350>;
+ };
+ };
+
+ pll_e_dvfs_opp_table: plle-opp-table {
+ compatible = "operating-points-v2";
+
+ opp@100000000,1000 {
+ opp-microvolt = <1000000 1000000 1350000>;
+ opp-hz = /bits/ 64 <100000000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_1000>;
+ };
+ };
+
+ pll_m_dvfs_opp_table: pllm-opp-table {
+ compatible = "operating-points-v2";
+
+ opp@533000000,950 {
+ opp-microvolt = <950000 950000 1350000>;
+ opp-hz = /bits/ 64 <533000000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_950>;
+ };
+
+ opp@667000000,1000 {
+ opp-microvolt = <1000000 1000000 1350000>;
+ opp-hz = /bits/ 64 <667000000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_1000>;
+ };
+
+ opp@800000000,1100 {
+ opp-microvolt = <1100000 1100000 1350000>;
+ opp-hz = /bits/ 64 <800000000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_1100>;
+ };
+
+ opp@1066000000,1200 {
+ opp-microvolt = <1200000 1200000 1350000>;
+ opp-hz = /bits/ 64 <1066000000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_1200>;
+ };
+ };
+
+ pwm_dvfs_opp_table: pwm-opp-table {
+ compatible = "operating-points-v2";
+
+ opp@408000000,1000 {
+ opp-microvolt = <1000000 1000000 1350000>;
+ opp-hz = /bits/ 64 <408000000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_1000>;
+ };
+ };
+
+ sata_dvfs_opp_table: sata-opp-table {
+ compatible = "operating-points-v2";
+
+ opp@216000000,1000 {
+ opp-microvolt = <1000000 1000000 1350000>;
+ opp-hz = /bits/ 64 <216000000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_1000>;
+ };
+ };
+
+ sata_oob_dvfs_opp_table: sata-oob-opp-table {
+ compatible = "operating-points-v2";
+
+ opp@216000000,1000 {
+ opp-microvolt = <1000000 1000000 1350000>;
+ opp-hz = /bits/ 64 <216000000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_1000>;
+ };
+ };
+
+ sbc1_dvfs_opp_table: sbc1-opp-table {
+ compatible = "operating-points-v2";
+
+ opp@52000000,1000 {
+ opp-microvolt = <1000000 1000000 1350000>;
+ opp-hz = /bits/ 64 <52000000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_1000>;
+ };
+
+ opp@60000000,1050 {
+ opp-microvolt = <1050000 1050000 1350000>;
+ opp-hz = /bits/ 64 <60000000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_1050>;
+ };
+
+ opp@100000000,1200 {
+ opp-microvolt = <1200000 1200000 1350000>;
+ opp-hz = /bits/ 64 <100000000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_1200>;
+ };
+ };
+
+ sbc2_dvfs_opp_table: sbc2-opp-table {
+ compatible = "operating-points-v2";
+
+ opp@52000000,1000 {
+ opp-microvolt = <1000000 1000000 1350000>;
+ opp-hz = /bits/ 64 <52000000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_1000>;
+ };
+
+ opp@60000000,1050 {
+ opp-microvolt = <1050000 1050000 1350000>;
+ opp-hz = /bits/ 64 <60000000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_1050>;
+ };
+
+ opp@100000000,1200 {
+ opp-microvolt = <1200000 1200000 1350000>;
+ opp-hz = /bits/ 64 <100000000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_1200>;
+ };
+ };
+
+ sbc3_dvfs_opp_table: sbc3-opp-table {
+ compatible = "operating-points-v2";
+
+ opp@52000000,1000 {
+ opp-microvolt = <1000000 1000000 1350000>;
+ opp-hz = /bits/ 64 <52000000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_1000>;
+ };
+
+ opp@60000000,1050 {
+ opp-microvolt = <1050000 1050000 1350000>;
+ opp-hz = /bits/ 64 <60000000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_1050>;
+ };
+
+ opp@100000000,1200 {
+ opp-microvolt = <1200000 1200000 1350000>;
+ opp-hz = /bits/ 64 <100000000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_1200>;
+ };
+ };
+
+ sbc4_dvfs_opp_table: sbc4-opp-table {
+ compatible = "operating-points-v2";
+
+ opp@52000000,1000 {
+ opp-microvolt = <1000000 1000000 1350000>;
+ opp-hz = /bits/ 64 <52000000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_1000>;
+ };
+
+ opp@60000000,1050 {
+ opp-microvolt = <1050000 1050000 1350000>;
+ opp-hz = /bits/ 64 <60000000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_1050>;
+ };
+
+ opp@100000000,1200 {
+ opp-microvolt = <1200000 1200000 1350000>;
+ opp-hz = /bits/ 64 <100000000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_1200>;
+ };
+ };
+
+ sbc5_dvfs_opp_table: sbc5-opp-table {
+ compatible = "operating-points-v2";
+
+ opp@52000000,1000 {
+ opp-microvolt = <1000000 1000000 1350000>;
+ opp-hz = /bits/ 64 <52000000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_1000>;
+ };
+
+ opp@60000000,1050 {
+ opp-microvolt = <1050000 1050000 1350000>;
+ opp-hz = /bits/ 64 <60000000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_1050>;
+ };
+
+ opp@100000000,1200 {
+ opp-microvolt = <1200000 1200000 1350000>;
+ opp-hz = /bits/ 64 <100000000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_1200>;
+ };
+ };
+
+ sbc6_dvfs_opp_table: sbc6-opp-table {
+ compatible = "operating-points-v2";
+
+ opp@52000000,1000 {
+ opp-microvolt = <1000000 1000000 1350000>;
+ opp-hz = /bits/ 64 <52000000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_1000>;
+ };
+
+ opp@60000000,1050 {
+ opp-microvolt = <1050000 1050000 1350000>;
+ opp-hz = /bits/ 64 <60000000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_1050>;
+ };
+
+ opp@100000000,1200 {
+ opp-microvolt = <1200000 1200000 1350000>;
+ opp-hz = /bits/ 64 <100000000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_1200>;
+ };
+ };
+
+ sdmmc1_dvfs_opp_table: sdmmc1-opp-table {
+ compatible = "operating-points-v2";
+
+ opp@104000000,950 {
+ opp-microvolt = <950000 950000 1350000>;
+ opp-hz = /bits/ 64 <104000000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_950>;
+ };
+
+ opp@208000000,1200 {
+ opp-microvolt = <1200000 1200000 1350000>;
+ opp-hz = /bits/ 64 <208000000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_1200>;
+ };
+ };
+
+ sdmmc3_dvfs_opp_table: sdmmc3-opp-table {
+ compatible = "operating-points-v2";
+
+ opp@104000000,950 {
+ opp-microvolt = <950000 950000 1350000>;
+ opp-hz = /bits/ 64 <104000000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_950>;
+ };
+
+ opp@208000000,1200 {
+ opp-microvolt = <1200000 1200000 1350000>;
+ opp-hz = /bits/ 64 <208000000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_1200>;
+ };
+ };
+
+ spdif_out_dvfs_opp_table: spdif-out-opp-table {
+ compatible = "operating-points-v2";
+
+ opp@26000000,1000 {
+ opp-microvolt = <1000000 1000000 1350000>;
+ opp-hz = /bits/ 64 <26000000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_1000>;
+ };
+ };
+
+ tvdac_dvfs_opp_table: tvdac-opp-table {
+ compatible = "operating-points-v2";
+
+ opp@220000000,1000 {
+ opp-microvolt = <1000000 1000000 1350000>;
+ opp-hz = /bits/ 64 <220000000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_1000>;
+ };
+ };
+
+ tvo_dvfs_opp_table: tvo-opp-table {
+ compatible = "operating-points-v2";
+
+ opp@297000000,1050 {
+ opp-microvolt = <1050000 1050000 1350000>;
+ opp-hz = /bits/ 64 <297000000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_1050>;
+ };
+ };
+
+ usbd_dvfs_opp_table: usbd-opp-table {
+ compatible = "operating-points-v2";
+
+ opp@480000000,1000 {
+ opp-microvolt = <1000000 1000000 1350000>;
+ opp-hz = /bits/ 64 <480000000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_1000>;
+ };
+ };
+
+ usb2_dvfs_opp_table: usb2-opp-table {
+ compatible = "operating-points-v2";
+
+ opp@480000000,1000 {
+ opp-microvolt = <1000000 1000000 1350000>;
+ opp-hz = /bits/ 64 <480000000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_1000>;
+ };
+ };
+
+ usb3_dvfs_opp_table: usb3-opp-table {
+ compatible = "operating-points-v2";
+
+ opp@480000000,1000 {
+ opp-microvolt = <1000000 1000000 1350000>;
+ opp-hz = /bits/ 64 <480000000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_1000>;
+ };
+ };
+
+ vde_dvfs_opp_table: vde-opp-table {
+ compatible = "operating-points-v2";
+
+ opp@228000000,1000 {
+ opp-microvolt = <1000000 1000000 1350000>;
+ opp-hz = /bits/ 64 <228000000>;
+ opp-supported-hw = <0x0003>;
+ required-opps = <&core_opp_1000>;
+ };
+
+ opp@247000000,1000 {
+ opp-microvolt = <1000000 1000000 1350000>;
+ opp-hz = /bits/ 64 <247000000>;
+ opp-supported-hw = <0x0004>;
+ required-opps = <&core_opp_1000>;
+ };
+
+ opp@275000000,1050 {
+ opp-microvolt = <1050000 1050000 1350000>;
+ opp-hz = /bits/ 64 <275000000>;
+ opp-supported-hw = <0x0003>;
+ required-opps = <&core_opp_1050>;
+ };
+
+ opp@304000000,1050 {
+ opp-microvolt = <1050000 1050000 1350000>;
+ opp-hz = /bits/ 64 <304000000>;
+ opp-supported-hw = <0x0004>;
+ required-opps = <&core_opp_1050>;
+ };
+
+ opp@332000000,1100 {
+ opp-microvolt = <1100000 1100000 1350000>;
+ opp-hz = /bits/ 64 <332000000>;
+ opp-supported-hw = <0x0003>;
+ required-opps = <&core_opp_1100>;
+ };
+
+ opp@352000000,1100 {
+ opp-microvolt = <1100000 1100000 1350000>;
+ opp-hz = /bits/ 64 <352000000>;
+ opp-supported-hw = <0x0004>;
+ required-opps = <&core_opp_1100>;
+ };
+
+ opp@380000000,1150 {
+ opp-microvolt = <1150000 1150000 1350000>;
+ opp-hz = /bits/ 64 <380000000>;
+ opp-supported-hw = <0x0003>;
+ required-opps = <&core_opp_1150>;
+ };
+
+ opp@400000000,1150 {
+ opp-microvolt = <1150000 1150000 1350000>;
+ opp-hz = /bits/ 64 <400000000>;
+ opp-supported-hw = <0x0004>;
+ required-opps = <&core_opp_1150>;
+ };
+
+ opp@416000000,1200 {
+ opp-microvolt = <1200000 1200000 1350000>;
+ opp-hz = /bits/ 64 <416000000>;
+ opp-supported-hw = <0x0003>;
+ required-opps = <&core_opp_1200>;
+ };
+
+ opp@437000000,1200 {
+ opp-microvolt = <1200000 1200000 1350000>;
+ opp-hz = /bits/ 64 <437000000>;
+ opp-supported-hw = <0x0004>;
+ required-opps = <&core_opp_1200>;
+ };
+
+ opp@484000000,1250 {
+ opp-microvolt = <1250000 1250000 1350000>;
+ opp-hz = /bits/ 64 <484000000>;
+ opp-supported-hw = <0x000C>;
+ required-opps = <&core_opp_1250>;
+ };
+
+ opp@520000000,1300 {
+ opp-microvolt = <1300000 1300000 1350000>;
+ opp-hz = /bits/ 64 <520000000>;
+ opp-supported-hw = <0x0004>;
+ required-opps = <&core_opp_1300>;
+ };
+
+ opp@600000000,1350 {
+ opp-microvolt = <1350000 1350000 1350000>;
+ opp-hz = /bits/ 64 <600000000>;
+ opp-supported-hw = <0x0004>;
+ required-opps = <&core_opp_1350>;
+ };
+ };
+
+ vi_dvfs_opp_table: vi-opp-table {
+ compatible = "operating-points-v2";
+
+ opp@216000000,1000 {
+ opp-microvolt = <1000000 1000000 1350000>;
+ opp-hz = /bits/ 64 <216000000>;
+ opp-supported-hw = <0x0003>;
+ required-opps = <&core_opp_1000>;
+ };
+
+ opp@219000000,1000 {
+ opp-microvolt = <1000000 1000000 1350000>;
+ opp-hz = /bits/ 64 <219000000>;
+ opp-supported-hw = <0x0004>;
+ required-opps = <&core_opp_1000>;
+ };
+
+ opp@267000000,1050 {
+ opp-microvolt = <1050000 1050000 1350000>;
+ opp-hz = /bits/ 64 <267000000>;
+ opp-supported-hw = <0x0006>;
+ required-opps = <&core_opp_1050>;
+ };
+
+ opp@285000000,1050 {
+ opp-microvolt = <1050000 1050000 1350000>;
+ opp-hz = /bits/ 64 <285000000>;
+ opp-supported-hw = <0x0001>;
+ required-opps = <&core_opp_1050>;
+ };
+
+ opp@300000000,1100 {
+ opp-microvolt = <1100000 1100000 1350000>;
+ opp-hz = /bits/ 64 <300000000>;
+ opp-supported-hw = <0x0007>;
+ required-opps = <&core_opp_1100>;
+ };
+
+ opp@371000000,1150 {
+ opp-microvolt = <1150000 1150000 1350000>;
+ opp-hz = /bits/ 64 <371000000>;
+ opp-supported-hw = <0x0006>;
+ required-opps = <&core_opp_1150>;
+ };
+
+ opp@409000000,1200 {
+ opp-microvolt = <1200000 1200000 1350000>;
+ opp-hz = /bits/ 64 <409000000>;
+ opp-supported-hw = <0x0006>;
+ required-opps = <&core_opp_1200>;
+ };
+
+ opp@425000000,1250 {
+ opp-microvolt = <1250000 1250000 1350000>;
+ opp-hz = /bits/ 64 <425000000>;
+ opp-supported-hw = <0x0004>;
+ required-opps = <&core_opp_1250>;
+ };
+
+ opp@470000000,1250 {
+ opp-microvolt = <1250000 1250000 1350000>;
+ opp-hz = /bits/ 64 <470000000>;
+ opp-supported-hw = <0x0008>;
+ required-opps = <&core_opp_1250>;
+ };
+ };
};
diff --git a/arch/arm/boot/dts/tegra30.dtsi b/arch/arm/boot/dts/tegra30.dtsi
index eaf4951d9ff8..c8130f8f680e 100644
--- a/arch/arm/boot/dts/tegra30.dtsi
+++ b/arch/arm/boot/dts/tegra30.dtsi
@@ -55,6 +55,8 @@ pcie@3000 {
<&tegra_car 72>,
<&tegra_car 74>;
reset-names = "pex", "afi", "pcie_x";
+ operating-points-v2 = <&pcie_dvfs_opp_table>;
+ power-domains = <&pd_core>;
status = "disabled";

pci@1,0 {
@@ -124,6 +126,8 @@ host1x@50000000 {
resets = <&tegra_car 28>;
reset-names = "host1x";
iommus = <&mc TEGRA_SWGROUP_HC>;
+ operating-points-v2 = <&host1x_dvfs_opp_table>;
+ power-domains = <&pd_heg>;

#address-cells = <1>;
#size-cells = <1>;
@@ -137,6 +141,8 @@ mpe@54040000 {
clocks = <&tegra_car TEGRA30_CLK_MPE>;
resets = <&tegra_car 60>;
reset-names = "mpe";
+ operating-points-v2 = <&mpe_dvfs_opp_table>;
+ power-domains = <&pd_mpe>;

iommus = <&mc TEGRA_SWGROUP_MPE>;
};
@@ -148,6 +154,8 @@ vi@54080000 {
clocks = <&tegra_car TEGRA30_CLK_VI>;
resets = <&tegra_car 20>;
reset-names = "vi";
+ operating-points-v2 = <&vi_dvfs_opp_table>;
+ power-domains = <&pd_venc>;

iommus = <&mc TEGRA_SWGROUP_VI>;
};
@@ -159,6 +167,8 @@ epp@540c0000 {
clocks = <&tegra_car TEGRA30_CLK_EPP>;
resets = <&tegra_car 19>;
reset-names = "epp";
+ operating-points-v2 = <&epp_dvfs_opp_table>;
+ power-domains = <&pd_heg>;

iommus = <&mc TEGRA_SWGROUP_EPP>;
};
@@ -170,6 +180,7 @@ isp@54100000 {
clocks = <&tegra_car TEGRA30_CLK_ISP>;
resets = <&tegra_car 23>;
reset-names = "isp";
+ power-domains = <&pd_venc>;

iommus = <&mc TEGRA_SWGROUP_ISP>;
};
@@ -181,6 +192,8 @@ gr2d@54140000 {
clocks = <&tegra_car TEGRA30_CLK_GR2D>;
resets = <&tegra_car 21>;
reset-names = "2d";
+ operating-points-v2 = <&gr2d_dvfs_opp_table>;
+ power-domains = <&pd_heg>;

iommus = <&mc TEGRA_SWGROUP_G2>;
};
@@ -194,6 +207,9 @@ gr3d@54180000 {
resets = <&tegra_car 24>,
<&tegra_car 98>;
reset-names = "3d", "3d2";
+ operating-points-v2 = <&gr3d_dvfs_opp_table>;
+ power-domains = <&pd_3d0>, <&pd_3d1>;
+ power-domain-names = "3d0", "3d1";

iommus = <&mc TEGRA_SWGROUP_NV>,
<&mc TEGRA_SWGROUP_NV2>;
@@ -208,6 +224,8 @@ dc@54200000 {
clock-names = "dc", "parent";
resets = <&tegra_car 27>;
reset-names = "dc";
+ operating-points-v2 = <&disp1_dvfs_opp_table>;
+ power-domains = <&pd_core>;

iommus = <&mc TEGRA_SWGROUP_DC>;

@@ -238,6 +256,8 @@ dc@54240000 {
clock-names = "dc", "parent";
resets = <&tegra_car 26>;
reset-names = "dc";
+ operating-points-v2 = <&disp2_dvfs_opp_table>;
+ power-domains = <&pd_core>;

iommus = <&mc TEGRA_SWGROUP_DCB>;

@@ -268,6 +288,8 @@ hdmi@54280000 {
clock-names = "hdmi", "parent";
resets = <&tegra_car 51>;
reset-names = "hdmi";
+ operating-points-v2 = <&hdmi_dvfs_opp_table>;
+ power-domains = <&pd_core>;
status = "disabled";
};

@@ -276,6 +298,8 @@ tvo@542c0000 {
reg = <0x542c0000 0x00040000>;
interrupts = <GIC_SPI 76 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&tegra_car TEGRA30_CLK_TVO>;
+ operating-points-v2 = <&tvo_dvfs_opp_table>;
+ power-domains = <&pd_core>;
status = "disabled";
};

@@ -287,6 +311,8 @@ dsi@54300000 {
clock-names = "dsi", "parent";
resets = <&tegra_car 48>;
reset-names = "dsi";
+ operating-points-v2 = <&dsia_dvfs_opp_table>;
+ power-domains = <&pd_core>;
status = "disabled";
};

@@ -298,6 +324,8 @@ dsi@54400000 {
clock-names = "dsi", "parent";
resets = <&tegra_car 84>;
reset-names = "dsi";
+ operating-points-v2 = <&dsib_dvfs_opp_table>;
+ power-domains = <&pd_core>;
status = "disabled";
};
};
@@ -358,6 +386,34 @@ tegra_car: clock@60006000 {
reg = <0x60006000 0x1000>;
#clock-cells = <1>;
#reset-cells = <1>;
+
+ sclk {
+ compatible = "nvidia,tegra30-sclk";
+ operating-points-v2 = <&sclk_dvfs_opp_table>;
+ clocks = <&tegra_car TEGRA30_CLK_SCLK>;
+ power-domains = <&pd_core>;
+ };
+
+ pll-c {
+ compatible = "nvidia,tegra30-pllc";
+ operating-points-v2 = <&pll_c_dvfs_opp_table>;
+ clocks = <&tegra_car TEGRA30_CLK_PLL_C>;
+ power-domains = <&pd_core>;
+ };
+
+ pll-e {
+ compatible = "nvidia,tegra30-plle";
+ operating-points-v2 = <&pll_e_dvfs_opp_table>;
+ clocks = <&tegra_car TEGRA30_CLK_PLL_E>;
+ power-domains = <&pd_core>;
+ };
+
+ pll-m {
+ compatible = "nvidia,tegra30-pllm";
+ operating-points-v2 = <&pll_m_dvfs_opp_table>;
+ clocks = <&tegra_car TEGRA30_CLK_PLL_M>;
+ power-domains = <&pd_core>;
+ };
};

flow-controller@60007000 {
@@ -468,6 +524,8 @@ vde@6001a000 {
reset-names = "vde", "mc";
resets = <&tegra_car 61>, <&mc TEGRA30_MC_RESET_VDE>;
iommus = <&mc TEGRA_SWGROUP_VDE>;
+ operating-points-v2 = <&vde_dvfs_opp_table>;
+ power-domains = <&pd_vde>;
};

apbmisc@70000800 {
@@ -565,6 +623,8 @@ gmi@70009000 {
clock-names = "gmi";
resets = <&tegra_car 42>;
reset-names = "gmi";
+ operating-points-v2 = <&nor_dvfs_opp_table>;
+ power-domains = <&pd_core>;
status = "disabled";
};

@@ -575,6 +635,8 @@ pwm: pwm@7000a000 {
clocks = <&tegra_car TEGRA30_CLK_PWM>;
resets = <&tegra_car 17>;
reset-names = "pwm";
+ operating-points-v2 = <&pwm_dvfs_opp_table>;
+ power-domains = <&pd_core>;
status = "disabled";
};

@@ -676,6 +738,8 @@ spi@7000d400 {
reset-names = "spi";
dmas = <&apbdma 15>, <&apbdma 15>;
dma-names = "rx", "tx";
+ operating-points-v2 = <&sbc1_dvfs_opp_table>;
+ power-domains = <&pd_core>;
status = "disabled";
};

@@ -690,6 +754,8 @@ spi@7000d600 {
reset-names = "spi";
dmas = <&apbdma 16>, <&apbdma 16>;
dma-names = "rx", "tx";
+ operating-points-v2 = <&sbc2_dvfs_opp_table>;
+ power-domains = <&pd_core>;
status = "disabled";
};

@@ -704,6 +770,8 @@ spi@7000d800 {
reset-names = "spi";
dmas = <&apbdma 17>, <&apbdma 17>;
dma-names = "rx", "tx";
+ operating-points-v2 = <&sbc3_dvfs_opp_table>;
+ power-domains = <&pd_core>;
status = "disabled";
};

@@ -718,6 +786,8 @@ spi@7000da00 {
reset-names = "spi";
dmas = <&apbdma 18>, <&apbdma 18>;
dma-names = "rx", "tx";
+ operating-points-v2 = <&sbc4_dvfs_opp_table>;
+ power-domains = <&pd_core>;
status = "disabled";
};

@@ -732,6 +802,8 @@ spi@7000dc00 {
reset-names = "spi";
dmas = <&apbdma 27>, <&apbdma 27>;
dma-names = "rx", "tx";
+ operating-points-v2 = <&sbc5_dvfs_opp_table>;
+ power-domains = <&pd_core>;
status = "disabled";
};

@@ -746,6 +818,8 @@ spi@7000de00 {
reset-names = "spi";
dmas = <&apbdma 28>, <&apbdma 28>;
dma-names = "rx", "tx";
+ operating-points-v2 = <&sbc6_dvfs_opp_table>;
+ power-domains = <&pd_core>;
status = "disabled";
};

@@ -765,6 +839,72 @@ tegra_pmc: pmc@7000e400 {
clocks = <&tegra_car TEGRA30_CLK_PCLK>, <&clk32k_in>;
clock-names = "pclk", "clk32k_in";
#clock-cells = <1>;
+
+ pd_core: core-domain {
+ operating-points-v2 = <&core_opp_table>;
+ #power-domain-cells = <0>;
+ };
+
+ powergates {
+ pd_3d0: td {
+ clocks = <&tegra_car TEGRA30_CLK_GR3D>;
+ resets = <&mc TEGRA30_MC_RESET_3D>,
+ <&tegra_car TEGRA30_CLK_GR3D>;
+ power-domains = <&pd_core>;
+ #power-domain-cells = <0>;
+ };
+
+ pd_3d1: td2 {
+ clocks = <&tegra_car TEGRA30_CLK_GR3D2>;
+ resets = <&mc TEGRA30_MC_RESET_3D2>,
+ <&tegra_car TEGRA30_CLK_GR3D2>;
+ power-domains = <&pd_core>;
+ #power-domain-cells = <0>;
+ };
+
+ pd_venc: venc {
+ clocks = <&tegra_car TEGRA30_CLK_ISP>,
+ <&tegra_car TEGRA30_CLK_VI>,
+ <&tegra_car TEGRA30_CLK_CSI>;
+ resets = <&mc TEGRA30_MC_RESET_ISP>,
+ <&mc TEGRA30_MC_RESET_VI>,
+ <&tegra_car TEGRA30_CLK_ISP>,
+ <&tegra_car 20 /* VI */>,
+ <&tegra_car TEGRA30_CLK_CSI>;
+ power-domains = <&pd_core>;
+ #power-domain-cells = <0>;
+ };
+
+ pd_vde: vdec {
+ clocks = <&tegra_car TEGRA30_CLK_VDE>;
+ resets = <&mc TEGRA30_MC_RESET_VDE>,
+ <&tegra_car TEGRA30_CLK_VDE>;
+ power-domains = <&pd_core>;
+ #power-domain-cells = <0>;
+ };
+
+ pd_mpe: mpe {
+ clocks = <&tegra_car TEGRA30_CLK_MPE>;
+ resets = <&mc TEGRA30_MC_RESET_MPE>,
+ <&tegra_car TEGRA30_CLK_MPE>;
+ power-domains = <&pd_core>;
+ #power-domain-cells = <0>;
+ };
+
+ pd_heg: heg {
+ clocks = <&tegra_car TEGRA30_CLK_GR2D>,
+ <&tegra_car TEGRA30_CLK_EPP>,
+ <&tegra_car TEGRA30_CLK_HOST1X>;
+ resets = <&mc TEGRA30_MC_RESET_2D>,
+ <&mc TEGRA30_MC_RESET_EPP>,
+ <&mc TEGRA30_MC_RESET_HC>,
+ <&tegra_car TEGRA30_CLK_GR2D>,
+ <&tegra_car TEGRA30_CLK_EPP>,
+ <&tegra_car TEGRA30_CLK_HOST1X>;
+ power-domains = <&pd_core>;
+ #power-domain-cells = <0>;
+ };
+ };
};

mc: memory-controller@7000f000 {
@@ -785,6 +925,7 @@ emc: memory-controller@7000f400 {
reg = <0x7000f400 0x400>;
interrupts = <GIC_SPI 78 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&tegra_car TEGRA30_CLK_EMC>;
+ power-domains = <&pd_core>;

nvidia,memory-controller = <&mc>;
operating-points-v2 = <&emc_icc_dvfs_opp_table>;
@@ -799,6 +940,8 @@ fuse@7000f800 {
clock-names = "fuse";
resets = <&tegra_car 39>;
reset-names = "fuse";
+ operating-points-v2 = <&fuse_burn_dvfs_opp_table>;
+ power-domains = <&pd_core>;
};

tsensor: tsensor@70014000 {
@@ -921,6 +1064,8 @@ mmc@78000000 {
clock-names = "sdhci";
resets = <&tegra_car 14>;
reset-names = "sdhci";
+ operating-points-v2 = <&sdmmc1_dvfs_opp_table>;
+ power-domains = <&pd_core>;
status = "disabled";
};

@@ -943,6 +1088,8 @@ mmc@78000400 {
clock-names = "sdhci";
resets = <&tegra_car 69>;
reset-names = "sdhci";
+ operating-points-v2 = <&sdmmc3_dvfs_opp_table>;
+ power-domains = <&pd_core>;
status = "disabled";
};

@@ -967,6 +1114,8 @@ usb@7d000000 {
reset-names = "usb";
nvidia,needs-double-reset;
nvidia,phy = <&phy1>;
+ operating-points-v2 = <&usbd_dvfs_opp_table>;
+ power-domains = <&pd_core>;
status = "disabled";
};

@@ -1006,6 +1155,8 @@ usb@7d004000 {
resets = <&tegra_car 58>;
reset-names = "usb";
nvidia,phy = <&phy2>;
+ operating-points-v2 = <&usb2_dvfs_opp_table>;
+ power-domains = <&pd_core>;
status = "disabled";
};

@@ -1044,6 +1195,8 @@ usb@7d008000 {
resets = <&tegra_car 59>;
reset-names = "usb";
nvidia,phy = <&phy3>;
+ operating-points-v2 = <&usb3_dvfs_opp_table>;
+ power-domains = <&pd_core>;
status = "disabled";
};

--
2.32.0

2021-09-26 22:46:24

by Dmitry Osipenko

[permalink] [raw]
Subject: [PATCH v13 27/35] soc/tegra: regulators: Prepare for suspend

Depending on hardware version, Tegra SoC may require a higher voltages
during resume from system suspend, otherwise hardware will crash. Set
SoC voltages to a nominal levels during suspend.

Signed-off-by: Dmitry Osipenko <[email protected]>
---
drivers/soc/tegra/regulators-tegra20.c | 99 ++++++++++++++++++++
drivers/soc/tegra/regulators-tegra30.c | 122 +++++++++++++++++++++++++
2 files changed, 221 insertions(+)

diff --git a/drivers/soc/tegra/regulators-tegra20.c b/drivers/soc/tegra/regulators-tegra20.c
index b8ce9fd0650d..6a2f90ab9d3e 100644
--- a/drivers/soc/tegra/regulators-tegra20.c
+++ b/drivers/soc/tegra/regulators-tegra20.c
@@ -16,7 +16,9 @@
#include <linux/regulator/coupler.h>
#include <linux/regulator/driver.h>
#include <linux/regulator/machine.h>
+#include <linux/suspend.h>

+#include <soc/tegra/fuse.h>
#include <soc/tegra/pmc.h>

struct tegra_regulator_coupler {
@@ -25,9 +27,12 @@ struct tegra_regulator_coupler {
struct regulator_dev *cpu_rdev;
struct regulator_dev *rtc_rdev;
struct notifier_block reboot_notifier;
+ struct notifier_block suspend_notifier;
int core_min_uV, cpu_min_uV;
bool sys_reboot_mode_req;
bool sys_reboot_mode;
+ bool sys_suspend_mode_req;
+ bool sys_suspend_mode;
};

static inline struct tegra_regulator_coupler *
@@ -105,6 +110,28 @@ static int tegra20_core_rtc_max_spread(struct regulator_dev *core_rdev,
return 150000;
}

+static int tegra20_cpu_nominal_uV(void)
+{
+ switch (tegra_sku_info.soc_speedo_id) {
+ case 0:
+ return 1100000;
+ case 1:
+ return 1025000;
+ default:
+ return 1125000;
+ }
+}
+
+static int tegra20_core_nominal_uV(void)
+{
+ switch (tegra_sku_info.soc_speedo_id) {
+ default:
+ return 1225000;
+ case 2:
+ return 1300000;
+ }
+}
+
static int tegra20_core_rtc_update(struct tegra_regulator_coupler *tegra,
struct regulator_dev *core_rdev,
struct regulator_dev *rtc_rdev,
@@ -144,6 +171,11 @@ static int tegra20_core_rtc_update(struct tegra_regulator_coupler *tegra,
if (err)
return err;

+ /* prepare voltage level for suspend */
+ if (tegra->sys_suspend_mode)
+ core_min_uV = clamp(tegra20_core_nominal_uV(),
+ core_min_uV, core_max_uV);
+
core_uV = regulator_get_voltage_rdev(core_rdev);
if (core_uV < 0)
return core_uV;
@@ -279,6 +311,11 @@ static int tegra20_cpu_voltage_update(struct tegra_regulator_coupler *tegra,
if (tegra->sys_reboot_mode)
cpu_min_uV = max(cpu_min_uV, tegra->cpu_min_uV);

+ /* prepare voltage level for suspend */
+ if (tegra->sys_suspend_mode)
+ cpu_min_uV = clamp(tegra20_cpu_nominal_uV(),
+ cpu_min_uV, cpu_max_uV);
+
if (cpu_min_uV > cpu_uV) {
err = tegra20_core_rtc_update(tegra, core_rdev, rtc_rdev,
cpu_uV, cpu_min_uV);
@@ -320,6 +357,7 @@ static int tegra20_regulator_balance_voltage(struct regulator_coupler *coupler,
}

tegra->sys_reboot_mode = READ_ONCE(tegra->sys_reboot_mode_req);
+ tegra->sys_suspend_mode = READ_ONCE(tegra->sys_suspend_mode_req);

if (rdev == cpu_rdev)
return tegra20_cpu_voltage_update(tegra, cpu_rdev,
@@ -334,6 +372,63 @@ static int tegra20_regulator_balance_voltage(struct regulator_coupler *coupler,
return -EPERM;
}

+static int tegra20_regulator_prepare_suspend(struct tegra_regulator_coupler *tegra,
+ bool sys_suspend_mode)
+{
+ int err;
+
+ if (!tegra->core_rdev || !tegra->rtc_rdev || !tegra->cpu_rdev)
+ return 0;
+
+ /*
+ * All power domains are enabled early during resume from suspend
+ * by GENPD core. Domains like VENC may require a higher voltage
+ * when enabled during resume from suspend. This also prepares
+ * hardware for resuming from LP0.
+ */
+
+ WRITE_ONCE(tegra->sys_suspend_mode_req, sys_suspend_mode);
+
+ err = regulator_sync_voltage_rdev(tegra->cpu_rdev);
+ if (err)
+ return err;
+
+ err = regulator_sync_voltage_rdev(tegra->core_rdev);
+ if (err)
+ return err;
+
+ return 0;
+}
+
+static int tegra20_regulator_suspend(struct notifier_block *notifier,
+ unsigned long mode, void *arg)
+{
+ struct tegra_regulator_coupler *tegra;
+ int ret = 0;
+
+ tegra = container_of(notifier, struct tegra_regulator_coupler,
+ suspend_notifier);
+
+ switch (mode) {
+ case PM_HIBERNATION_PREPARE:
+ case PM_RESTORE_PREPARE:
+ case PM_SUSPEND_PREPARE:
+ ret = tegra20_regulator_prepare_suspend(tegra, true);
+ break;
+
+ case PM_POST_HIBERNATION:
+ case PM_POST_RESTORE:
+ case PM_POST_SUSPEND:
+ ret = tegra20_regulator_prepare_suspend(tegra, false);
+ break;
+ }
+
+ if (ret)
+ pr_err("failed to prepare regulators: %d\n", ret);
+
+ return notifier_from_errno(ret);
+}
+
static int tegra20_regulator_prepare_reboot(struct tegra_regulator_coupler *tegra,
bool sys_reboot_mode)
{
@@ -444,6 +539,7 @@ static struct tegra_regulator_coupler tegra20_coupler = {
.balance_voltage = tegra20_regulator_balance_voltage,
},
.reboot_notifier.notifier_call = tegra20_regulator_reboot,
+ .suspend_notifier.notifier_call = tegra20_regulator_suspend,
};

static int __init tegra_regulator_coupler_init(void)
@@ -456,6 +552,9 @@ static int __init tegra_regulator_coupler_init(void)
err = register_reboot_notifier(&tegra20_coupler.reboot_notifier);
WARN_ON(err);

+ err = register_pm_notifier(&tegra20_coupler.suspend_notifier);
+ WARN_ON(err);
+
return regulator_coupler_register(&tegra20_coupler.coupler);
}
arch_initcall(tegra_regulator_coupler_init);
diff --git a/drivers/soc/tegra/regulators-tegra30.c b/drivers/soc/tegra/regulators-tegra30.c
index e74bbc9c7859..8fd43c689134 100644
--- a/drivers/soc/tegra/regulators-tegra30.c
+++ b/drivers/soc/tegra/regulators-tegra30.c
@@ -16,6 +16,7 @@
#include <linux/regulator/coupler.h>
#include <linux/regulator/driver.h>
#include <linux/regulator/machine.h>
+#include <linux/suspend.h>

#include <soc/tegra/fuse.h>
#include <soc/tegra/pmc.h>
@@ -25,9 +26,12 @@ struct tegra_regulator_coupler {
struct regulator_dev *core_rdev;
struct regulator_dev *cpu_rdev;
struct notifier_block reboot_notifier;
+ struct notifier_block suspend_notifier;
int core_min_uV, cpu_min_uV;
bool sys_reboot_mode_req;
bool sys_reboot_mode;
+ bool sys_suspend_mode_req;
+ bool sys_suspend_mode;
};

static inline struct tegra_regulator_coupler *
@@ -113,6 +117,52 @@ static int tegra30_core_cpu_limit(int cpu_uV)
return -EINVAL;
}

+static int tegra30_cpu_nominal_uV(void)
+{
+ switch (tegra_sku_info.cpu_speedo_id) {
+ case 10 ... 11:
+ return 850000;
+
+ case 9:
+ return 912000;
+
+ case 1 ... 3:
+ case 7 ... 8:
+ return 1050000;
+
+ default:
+ return 1125000;
+
+ case 4 ... 6:
+ case 12 ... 13:
+ return 1237000;
+ }
+}
+
+static int tegra30_core_nominal_uV(void)
+{
+ switch (tegra_sku_info.soc_speedo_id) {
+ case 0:
+ return 1200000;
+
+ case 1:
+ if (tegra_sku_info.cpu_speedo_id != 7 &&
+ tegra_sku_info.cpu_speedo_id != 8)
+ return 1200000;
+
+ fallthrough;
+
+ case 2:
+ if (tegra_sku_info.cpu_speedo_id != 13)
+ return 1300000;
+
+ return 1350000;
+
+ default:
+ return 1250000;
+ }
+}
+
static int tegra30_voltage_update(struct tegra_regulator_coupler *tegra,
struct regulator_dev *cpu_rdev,
struct regulator_dev *core_rdev)
@@ -168,6 +218,11 @@ static int tegra30_voltage_update(struct tegra_regulator_coupler *tegra,
if (err)
return err;

+ /* prepare voltage level for suspend */
+ if (tegra->sys_suspend_mode)
+ core_min_uV = clamp(tegra30_core_nominal_uV(),
+ core_min_uV, core_max_uV);
+
core_uV = regulator_get_voltage_rdev(core_rdev);
if (core_uV < 0)
return core_uV;
@@ -223,6 +278,11 @@ static int tegra30_voltage_update(struct tegra_regulator_coupler *tegra,
if (tegra->sys_reboot_mode)
cpu_min_uV = max(cpu_min_uV, tegra->cpu_min_uV);

+ /* prepare voltage level for suspend */
+ if (tegra->sys_suspend_mode)
+ cpu_min_uV = clamp(tegra30_cpu_nominal_uV(),
+ cpu_min_uV, cpu_max_uV);
+
if (core_min_limited_uV > core_uV) {
pr_err("core voltage constraint violated: %d %d %d\n",
core_uV, core_min_limited_uV, cpu_uV);
@@ -292,10 +352,68 @@ static int tegra30_regulator_balance_voltage(struct regulator_coupler *coupler,
}

tegra->sys_reboot_mode = READ_ONCE(tegra->sys_reboot_mode_req);
+ tegra->sys_suspend_mode = READ_ONCE(tegra->sys_suspend_mode_req);

return tegra30_voltage_update(tegra, cpu_rdev, core_rdev);
}

+static int tegra30_regulator_prepare_suspend(struct tegra_regulator_coupler *tegra,
+ bool sys_suspend_mode)
+{
+ int err;
+
+ if (!tegra->core_rdev || !tegra->cpu_rdev)
+ return 0;
+
+ /*
+ * All power domains are enabled early during resume from suspend
+ * by GENPD core. Domains like VENC may require a higher voltage
+ * when enabled during resume from suspend. This also prepares
+ * hardware for resuming from LP0.
+ */
+
+ WRITE_ONCE(tegra->sys_suspend_mode_req, sys_suspend_mode);
+
+ err = regulator_sync_voltage_rdev(tegra->cpu_rdev);
+ if (err)
+ return err;
+
+ err = regulator_sync_voltage_rdev(tegra->core_rdev);
+ if (err)
+ return err;
+
+ return 0;
+}
+
+static int tegra30_regulator_suspend(struct notifier_block *notifier,
+ unsigned long mode, void *arg)
+{
+ struct tegra_regulator_coupler *tegra;
+ int ret = 0;
+
+ tegra = container_of(notifier, struct tegra_regulator_coupler,
+ suspend_notifier);
+
+ switch (mode) {
+ case PM_HIBERNATION_PREPARE:
+ case PM_RESTORE_PREPARE:
+ case PM_SUSPEND_PREPARE:
+ ret = tegra30_regulator_prepare_suspend(tegra, true);
+ break;
+
+ case PM_POST_HIBERNATION:
+ case PM_POST_RESTORE:
+ case PM_POST_SUSPEND:
+ ret = tegra30_regulator_prepare_suspend(tegra, false);
+ break;
+ }
+
+ if (ret)
+ pr_err("failed to prepare regulators: %d\n", ret);
+
+ return notifier_from_errno(ret);
+}
+
static int tegra30_regulator_prepare_reboot(struct tegra_regulator_coupler *tegra,
bool sys_reboot_mode)
{
@@ -395,6 +513,7 @@ static struct tegra_regulator_coupler tegra30_coupler = {
.balance_voltage = tegra30_regulator_balance_voltage,
},
.reboot_notifier.notifier_call = tegra30_regulator_reboot,
+ .suspend_notifier.notifier_call = tegra30_regulator_suspend,
};

static int __init tegra_regulator_coupler_init(void)
@@ -407,6 +526,9 @@ static int __init tegra_regulator_coupler_init(void)
err = register_reboot_notifier(&tegra30_coupler.reboot_notifier);
WARN_ON(err);

+ err = register_pm_notifier(&tegra30_coupler.suspend_notifier);
+ WARN_ON(err);
+
return regulator_coupler_register(&tegra30_coupler.coupler);
}
arch_initcall(tegra_regulator_coupler_init);
--
2.32.0

2021-09-26 22:46:32

by Dmitry Osipenko

[permalink] [raw]
Subject: [PATCH v13 16/35] usb: chipidea: tegra: Add runtime PM and OPP support

The Tegra USB controller belongs to the core power domain and we're going
to enable GENPD support for the core domain. Now USB controller must be
resumed using runtime PM API in order to initialize the USB power state.
We already support runtime PM for the CI device, but CI's PM is separated
from the RPM managed by tegra-usb driver. Add runtime PM and OPP support
to the driver.

Signed-off-by: Dmitry Osipenko <[email protected]>
---
drivers/usb/chipidea/ci_hdrc_tegra.c | 53 ++++++++++++++++++++++++----
1 file changed, 46 insertions(+), 7 deletions(-)

diff --git a/drivers/usb/chipidea/ci_hdrc_tegra.c b/drivers/usb/chipidea/ci_hdrc_tegra.c
index 60361141ac04..3142ef7ebe42 100644
--- a/drivers/usb/chipidea/ci_hdrc_tegra.c
+++ b/drivers/usb/chipidea/ci_hdrc_tegra.c
@@ -7,6 +7,7 @@
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of_device.h>
+#include <linux/pm_runtime.h>
#include <linux/reset.h>

#include <linux/usb.h>
@@ -15,6 +16,8 @@
#include <linux/usb/of.h>
#include <linux/usb/phy.h>

+#include <soc/tegra/common.h>
+
#include "../host/ehci.h"

#include "ci.h"
@@ -278,6 +281,8 @@ static int tegra_usb_probe(struct platform_device *pdev)
if (!usb)
return -ENOMEM;

+ platform_set_drvdata(pdev, usb);
+
soc = of_device_get_match_data(&pdev->dev);
if (!soc) {
dev_err(&pdev->dev, "failed to match OF data\n");
@@ -296,11 +301,17 @@ static int tegra_usb_probe(struct platform_device *pdev)
return err;
}

- err = clk_prepare_enable(usb->clk);
- if (err < 0) {
- dev_err(&pdev->dev, "failed to enable clock: %d\n", err);
+ err = devm_pm_runtime_enable(&pdev->dev);
+ if (err)
+ return err;
+
+ err = devm_tegra_core_dev_init_opp_table_common(&pdev->dev);
+ if (err)
+ return err;
+
+ err = pm_runtime_resume_and_get(&pdev->dev);
+ if (err)
return err;
- }

if (device_property_present(&pdev->dev, "nvidia,needs-double-reset"))
usb->needs_double_reset = true;
@@ -320,8 +331,6 @@ static int tegra_usb_probe(struct platform_device *pdev)
if (err)
goto fail_power_off;

- platform_set_drvdata(pdev, usb);
-
/* setup and register ChipIdea HDRC device */
usb->soc = soc;
usb->data.name = "tegra-usb";
@@ -350,7 +359,8 @@ static int tegra_usb_probe(struct platform_device *pdev)
phy_shutdown:
usb_phy_shutdown(usb->phy);
fail_power_off:
- clk_disable_unprepare(usb->clk);
+ pm_runtime_put(&pdev->dev);
+
return err;
}

@@ -360,15 +370,44 @@ static int tegra_usb_remove(struct platform_device *pdev)

ci_hdrc_remove_device(usb->dev);
usb_phy_shutdown(usb->phy);
+ pm_runtime_put(&pdev->dev);
+
+ return 0;
+}
+
+static int __maybe_unused tegra_usb_runtime_resume(struct device *dev)
+{
+ struct tegra_usb *usb = dev_get_drvdata(dev);
+ int err;
+
+ err = clk_prepare_enable(usb->clk);
+ if (err < 0) {
+ dev_err(dev, "failed to enable clock: %d\n", err);
+ return err;
+ }
+
+ return 0;
+}
+
+static int __maybe_unused tegra_usb_runtime_suspend(struct device *dev)
+{
+ struct tegra_usb *usb = dev_get_drvdata(dev);
+
clk_disable_unprepare(usb->clk);

return 0;
}

+static const struct dev_pm_ops tegra_usb_pm = {
+ SET_RUNTIME_PM_OPS(tegra_usb_runtime_suspend, tegra_usb_runtime_resume,
+ NULL)
+};
+
static struct platform_driver tegra_usb_driver = {
.driver = {
.name = "tegra-usb",
.of_match_table = tegra_usb_of_match,
+ .pm = &tegra_usb_pm,
},
.probe = tegra_usb_probe,
.remove = tegra_usb_remove,
--
2.32.0

2021-09-26 22:46:39

by Dmitry Osipenko

[permalink] [raw]
Subject: [PATCH v13 28/35] soc/tegra: pmc: Rename 3d power domains

Device-tree schema doesn't allow domain name to start with a number.
We don't use 3d domain yet in device-trees, so rename it to the name
used by Tegra TRMs: TD, TD2.

Reported-by: David Heidelberg <[email protected]>
Signed-off-by: Dmitry Osipenko <[email protected]>
---
drivers/soc/tegra/pmc.c | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/drivers/soc/tegra/pmc.c b/drivers/soc/tegra/pmc.c
index fb8faf7b226a..6759f19b9cd6 100644
--- a/drivers/soc/tegra/pmc.c
+++ b/drivers/soc/tegra/pmc.c
@@ -2964,7 +2964,7 @@ static SIMPLE_DEV_PM_OPS(tegra_pmc_pm_ops, tegra_pmc_suspend, tegra_pmc_resume);

static const char * const tegra20_powergates[] = {
[TEGRA_POWERGATE_CPU] = "cpu",
- [TEGRA_POWERGATE_3D] = "3d",
+ [TEGRA_POWERGATE_3D] = "td",
[TEGRA_POWERGATE_VENC] = "venc",
[TEGRA_POWERGATE_VDEC] = "vdec",
[TEGRA_POWERGATE_PCIE] = "pcie",
@@ -3072,7 +3072,7 @@ static const struct tegra_pmc_soc tegra20_pmc_soc = {

static const char * const tegra30_powergates[] = {
[TEGRA_POWERGATE_CPU] = "cpu0",
- [TEGRA_POWERGATE_3D] = "3d0",
+ [TEGRA_POWERGATE_3D] = "td",
[TEGRA_POWERGATE_VENC] = "venc",
[TEGRA_POWERGATE_VDEC] = "vdec",
[TEGRA_POWERGATE_PCIE] = "pcie",
@@ -3084,7 +3084,7 @@ static const char * const tegra30_powergates[] = {
[TEGRA_POWERGATE_CPU2] = "cpu2",
[TEGRA_POWERGATE_CPU3] = "cpu3",
[TEGRA_POWERGATE_CELP] = "celp",
- [TEGRA_POWERGATE_3D1] = "3d1",
+ [TEGRA_POWERGATE_3D1] = "td2",
};

static const u8 tegra30_cpu_powergates[] = {
@@ -3133,7 +3133,7 @@ static const struct tegra_pmc_soc tegra30_pmc_soc = {

static const char * const tegra114_powergates[] = {
[TEGRA_POWERGATE_CPU] = "crail",
- [TEGRA_POWERGATE_3D] = "3d",
+ [TEGRA_POWERGATE_3D] = "td",
[TEGRA_POWERGATE_VENC] = "venc",
[TEGRA_POWERGATE_VDEC] = "vdec",
[TEGRA_POWERGATE_MPE] = "mpe",
--
2.32.0

2021-09-26 22:46:46

by Dmitry Osipenko

[permalink] [raw]
Subject: [PATCH v13 34/35] ARM: tegra: Add Memory Client resets to Tegra30 GR2D, GR3D and Host1x

Memory access must be blocked before hardware reset is asserted and before
power is gated, otherwise a serious hardware fault is inevitable. Add
reset for memory clients to the GR2D, GR3D and Host1x nodes.

Tested-by: Peter Geis <[email protected]> # Ouya T30
Tested-by: Matt Merhar <[email protected]> # Ouya T30
Signed-off-by: Dmitry Osipenko <[email protected]>
---
arch/arm/boot/dts/tegra30.dtsi | 14 ++++++++------
1 file changed, 8 insertions(+), 6 deletions(-)

diff --git a/arch/arm/boot/dts/tegra30.dtsi b/arch/arm/boot/dts/tegra30.dtsi
index c8130f8f680e..09cb1ab75312 100644
--- a/arch/arm/boot/dts/tegra30.dtsi
+++ b/arch/arm/boot/dts/tegra30.dtsi
@@ -123,8 +123,8 @@ host1x@50000000 {
interrupt-names = "syncpt", "host1x";
clocks = <&tegra_car TEGRA30_CLK_HOST1X>;
clock-names = "host1x";
- resets = <&tegra_car 28>;
- reset-names = "host1x";
+ resets = <&tegra_car 28>, <&mc TEGRA30_MC_RESET_HC>;
+ reset-names = "host1x", "mc";
iommus = <&mc TEGRA_SWGROUP_HC>;
operating-points-v2 = <&host1x_dvfs_opp_table>;
power-domains = <&pd_heg>;
@@ -190,8 +190,8 @@ gr2d@54140000 {
reg = <0x54140000 0x00040000>;
interrupts = <GIC_SPI 72 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&tegra_car TEGRA30_CLK_GR2D>;
- resets = <&tegra_car 21>;
- reset-names = "2d";
+ resets = <&tegra_car 21>, <&mc TEGRA30_MC_RESET_2D>;
+ reset-names = "2d", "mc";
operating-points-v2 = <&gr2d_dvfs_opp_table>;
power-domains = <&pd_heg>;

@@ -205,8 +205,10 @@ gr3d@54180000 {
<&tegra_car TEGRA30_CLK_GR3D2>;
clock-names = "3d", "3d2";
resets = <&tegra_car 24>,
- <&tegra_car 98>;
- reset-names = "3d", "3d2";
+ <&tegra_car 98>,
+ <&mc TEGRA30_MC_RESET_3D>,
+ <&mc TEGRA30_MC_RESET_3D2>;
+ reset-names = "3d", "3d2", "mc", "mc2";
operating-points-v2 = <&gr3d_dvfs_opp_table>;
power-domains = <&pd_3d0>, <&pd_3d1>;
power-domain-names = "3d0", "3d1";
--
2.32.0

2021-09-26 22:47:01

by Dmitry Osipenko

[permalink] [raw]
Subject: [PATCH v13 31/35] ARM: tegra: Add OPP tables and power domains to Tegra20 device-trees

Add OPP tables and power domains to all peripheral devices which
support power management on Tegra20 SoC.

Tested-by: Paul Fertser <[email protected]> # PAZ00 T20
Tested-by: Nicolas Chauvet <[email protected]> # PAZ00 T20
Signed-off-by: Dmitry Osipenko <[email protected]>
---
.../boot/dts/tegra20-acer-a500-picasso.dts | 1 +
arch/arm/boot/dts/tegra20-colibri.dtsi | 3 +-
arch/arm/boot/dts/tegra20-harmony.dts | 3 +-
arch/arm/boot/dts/tegra20-paz00.dts | 1 +
.../arm/boot/dts/tegra20-peripherals-opp.dtsi | 941 ++++++++++++++++++
arch/arm/boot/dts/tegra20-seaboard.dts | 3 +-
arch/arm/boot/dts/tegra20-tamonten.dtsi | 3 +-
arch/arm/boot/dts/tegra20-trimslice.dts | 9 +
arch/arm/boot/dts/tegra20-ventana.dts | 1 +
arch/arm/boot/dts/tegra20.dtsi | 100 ++
10 files changed, 1061 insertions(+), 4 deletions(-)

diff --git a/arch/arm/boot/dts/tegra20-acer-a500-picasso.dts b/arch/arm/boot/dts/tegra20-acer-a500-picasso.dts
index 2280d75b66ab..1dbeabcb8b74 100644
--- a/arch/arm/boot/dts/tegra20-acer-a500-picasso.dts
+++ b/arch/arm/boot/dts/tegra20-acer-a500-picasso.dts
@@ -713,6 +713,7 @@ pmc@7000e400 {
nvidia,core-pwr-good-time = <3845 3845>;
nvidia,core-pwr-off-time = <458>;
nvidia,sys-clock-req-active-high;
+ core-supply = <&vdd_core>;
};

usb@c5000000 {
diff --git a/arch/arm/boot/dts/tegra20-colibri.dtsi b/arch/arm/boot/dts/tegra20-colibri.dtsi
index 585a5b441cf6..4ec403aa5f2e 100644
--- a/arch/arm/boot/dts/tegra20-colibri.dtsi
+++ b/arch/arm/boot/dts/tegra20-colibri.dtsi
@@ -495,7 +495,7 @@ reg_3v3_vsys: sys {
regulator-always-on;
};

- sm0 {
+ vdd_core: sm0 {
regulator-name = "VDD_CORE_1.2V";
regulator-min-microvolt = <1200000>;
regulator-max-microvolt = <1200000>;
@@ -601,6 +601,7 @@ pmc@7000e400 {
nvidia,core-pwr-good-time = <3845 3845>;
nvidia,core-pwr-off-time = <3875>;
nvidia,sys-clock-req-active-high;
+ core-supply = <&vdd_core>;

/* Set SLEEP MODE bit in SUPPLYENE register of TPS658643 PMIC */
i2c-thermtrip {
diff --git a/arch/arm/boot/dts/tegra20-harmony.dts b/arch/arm/boot/dts/tegra20-harmony.dts
index ae4312eedcbd..b21bab437ebd 100644
--- a/arch/arm/boot/dts/tegra20-harmony.dts
+++ b/arch/arm/boot/dts/tegra20-harmony.dts
@@ -339,7 +339,7 @@ sys_reg: sys {
regulator-always-on;
};

- sm0 {
+ vdd_core: sm0 {
regulator-name = "vdd_sm0,vdd_core";
regulator-min-microvolt = <1200000>;
regulator-max-microvolt = <1200000>;
@@ -565,6 +565,7 @@ pmc@7000e400 {
nvidia,core-pwr-good-time = <3845 3845>;
nvidia,core-pwr-off-time = <3875>;
nvidia,sys-clock-req-active-high;
+ core-supply = <&vdd_core>;
};

pcie@80003000 {
diff --git a/arch/arm/boot/dts/tegra20-paz00.dts b/arch/arm/boot/dts/tegra20-paz00.dts
index acc816bfd233..c8e4439d3dfb 100644
--- a/arch/arm/boot/dts/tegra20-paz00.dts
+++ b/arch/arm/boot/dts/tegra20-paz00.dts
@@ -519,6 +519,7 @@ pmc@7000e400 {
nvidia,core-pwr-good-time = <3845 3845>;
nvidia,core-pwr-off-time = <0>;
nvidia,sys-clock-req-active-high;
+ core-supply = <&core_vdd_reg>;
};

usb@c5000000 {
diff --git a/arch/arm/boot/dts/tegra20-peripherals-opp.dtsi b/arch/arm/boot/dts/tegra20-peripherals-opp.dtsi
index ef3ad2e5f270..ff8c3dcba8e9 100644
--- a/arch/arm/boot/dts/tegra20-peripherals-opp.dtsi
+++ b/arch/arm/boot/dts/tegra20-peripherals-opp.dtsi
@@ -1,6 +1,46 @@
// SPDX-License-Identifier: GPL-2.0

/ {
+ core_opp_table: core-power-domain-opp-table {
+ compatible = "operating-points-v2";
+ opp-shared;
+
+ core_opp_950: opp@950000 {
+ opp-microvolt = <950000 950000 1300000>;
+ opp-level = <950000>;
+ };
+
+ core_opp_1000: opp@1000000 {
+ opp-microvolt = <1000000 1000000 1300000>;
+ opp-level = <1000000>;
+ };
+
+ core_opp_1100: opp@1100000 {
+ opp-microvolt = <1100000 1100000 1300000>;
+ opp-level = <1100000>;
+ };
+
+ core_opp_1200: opp@1200000 {
+ opp-microvolt = <1200000 1200000 1300000>;
+ opp-level = <1200000>;
+ };
+
+ core_opp_1225: opp@1225000 {
+ opp-microvolt = <1225000 1225000 1300000>;
+ opp-level = <1225000>;
+ };
+
+ core_opp_1275: opp@1275000 {
+ opp-microvolt = <1275000 1275000 1300000>;
+ opp-level = <1275000>;
+ };
+
+ core_opp_1300: opp@1300000 {
+ opp-microvolt = <1300000 1300000 1300000>;
+ opp-level = <1300000>;
+ };
+ };
+
emc_icc_dvfs_opp_table: emc-dvfs-opp-table {
compatible = "operating-points-v2";

@@ -8,66 +48,77 @@ opp@36000000 {
opp-microvolt = <950000 950000 1300000>;
opp-hz = /bits/ 64 <36000000>;
opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_950>;
};

opp@47500000 {
opp-microvolt = <950000 950000 1300000>;
opp-hz = /bits/ 64 <47500000>;
opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_950>;
};

opp@50000000 {
opp-microvolt = <950000 950000 1300000>;
opp-hz = /bits/ 64 <50000000>;
opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_950>;
};

opp@54000000 {
opp-microvolt = <950000 950000 1300000>;
opp-hz = /bits/ 64 <54000000>;
opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_950>;
};

opp@57000000 {
opp-microvolt = <950000 950000 1300000>;
opp-hz = /bits/ 64 <57000000>;
opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_950>;
};

opp@100000000 {
opp-microvolt = <1000000 1000000 1300000>;
opp-hz = /bits/ 64 <100000000>;
opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_1000>;
};

opp@108000000 {
opp-microvolt = <1000000 1000000 1300000>;
opp-hz = /bits/ 64 <108000000>;
opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_1000>;
};

opp@126666000 {
opp-microvolt = <1000000 1000000 1300000>;
opp-hz = /bits/ 64 <126666000>;
opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_1000>;
};

opp@150000000 {
opp-microvolt = <1000000 1000000 1300000>;
opp-hz = /bits/ 64 <150000000>;
opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_1000>;
};

opp@190000000 {
opp-microvolt = <1000000 1000000 1300000>;
opp-hz = /bits/ 64 <190000000>;
opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_1000>;
};

opp@216000000 {
opp-microvolt = <1000000 1000000 1300000>;
opp-hz = /bits/ 64 <216000000>;
opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_1000>;
opp-suspend;
};

@@ -75,36 +126,926 @@ opp@300000000 {
opp-microvolt = <1000000 1000000 1300000>;
opp-hz = /bits/ 64 <300000000>;
opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_1000>;
};

opp@333000000 {
opp-microvolt = <1000000 1000000 1300000>;
opp-hz = /bits/ 64 <333000000>;
opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_1000>;
};

opp@380000000 {
opp-microvolt = <1100000 1100000 1300000>;
opp-hz = /bits/ 64 <380000000>;
opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_1100>;
};

opp@600000000 {
opp-microvolt = <1200000 1200000 1300000>;
opp-hz = /bits/ 64 <600000000>;
opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_1200>;
};

opp@666000000 {
opp-microvolt = <1200000 1200000 1300000>;
opp-hz = /bits/ 64 <666000000>;
opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_1200>;
};

opp@760000000 {
opp-microvolt = <1300000 1300000 1300000>;
opp-hz = /bits/ 64 <760000000>;
opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_1300>;
+ };
+ };
+
+ gr2d_dvfs_opp_table: gr2d-opp-table {
+ compatible = "operating-points-v2";
+
+ opp@133000000,950 {
+ opp-microvolt = <950000 950000 1300000>;
+ opp-hz = /bits/ 64 <133000000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_950>;
+ };
+
+ opp@171000000,1000 {
+ opp-microvolt = <1000000 1000000 1300000>;
+ opp-hz = /bits/ 64 <171000000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_1000>;
+ };
+
+ opp@247000000,1100 {
+ opp-microvolt = <1100000 1100000 1300000>;
+ opp-hz = /bits/ 64 <247000000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_1100>;
+ };
+
+ opp@300000000,1200 {
+ opp-microvolt = <1200000 1200000 1300000>;
+ opp-hz = /bits/ 64 <300000000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_1200>;
+ };
+ };
+
+ gr3d_dvfs_opp_table: gr3d-opp-table {
+ compatible = "operating-points-v2";
+
+ opp@114000000,950 {
+ opp-microvolt = <950000 950000 1300000>;
+ opp-hz = /bits/ 64 <114000000>;
+ opp-supported-hw = <0x0001>;
+ required-opps = <&core_opp_950>;
+ };
+
+ opp@161500000,1000 {
+ opp-microvolt = <1000000 1000000 1300000>;
+ opp-hz = /bits/ 64 <161500000>;
+ opp-supported-hw = <0x0001>;
+ required-opps = <&core_opp_1000>;
+ };
+
+ opp@161500000,950 {
+ opp-microvolt = <950000 950000 1300000>;
+ opp-hz = /bits/ 64 <161500000>;
+ opp-supported-hw = <0x0002>;
+ required-opps = <&core_opp_950>;
+ };
+
+ opp@209000000,1000 {
+ opp-microvolt = <1000000 1000000 1300000>;
+ opp-hz = /bits/ 64 <209000000>;
+ opp-supported-hw = <0x0002>;
+ required-opps = <&core_opp_1000>;
+ };
+
+ opp@218500000,950 {
+ opp-microvolt = <950000 950000 1300000>;
+ opp-hz = /bits/ 64 <218500000>;
+ opp-supported-hw = <0x0004>;
+ required-opps = <&core_opp_950>;
+ };
+
+ opp@247000000,1100 {
+ opp-microvolt = <1100000 1100000 1300000>;
+ opp-hz = /bits/ 64 <247000000>;
+ opp-supported-hw = <0x0001>;
+ required-opps = <&core_opp_1100>;
+ };
+
+ opp@247000000,950 {
+ opp-microvolt = <950000 950000 1300000>;
+ opp-hz = /bits/ 64 <247000000>;
+ opp-supported-hw = <0x0008>;
+ required-opps = <&core_opp_950>;
+ };
+
+ opp@256500000,1000 {
+ opp-microvolt = <1000000 1000000 1300000>;
+ opp-hz = /bits/ 64 <256500000>;
+ opp-supported-hw = <0x0004>;
+ required-opps = <&core_opp_1000>;
+ };
+
+ opp@285000000,1100 {
+ opp-microvolt = <1100000 1100000 1300000>;
+ opp-hz = /bits/ 64 <285000000>;
+ opp-supported-hw = <0x0002>;
+ required-opps = <&core_opp_1100>;
+ };
+
+ opp@285000000,1000 {
+ opp-microvolt = <1000000 1000000 1300000>;
+ opp-hz = /bits/ 64 <285000000>;
+ opp-supported-hw = <0x0008>;
+ required-opps = <&core_opp_1000>;
+ };
+
+ opp@304000000,1200 {
+ opp-microvolt = <1200000 1200000 1300000>;
+ opp-hz = /bits/ 64 <304000000>;
+ opp-supported-hw = <0x0001>;
+ required-opps = <&core_opp_1200>;
+ };
+
+ opp@323000000,1100 {
+ opp-microvolt = <1100000 1100000 1300000>;
+ opp-hz = /bits/ 64 <323000000>;
+ opp-supported-hw = <0x0004>;
+ required-opps = <&core_opp_1100>;
+ };
+
+ opp@333500000,1275 {
+ opp-microvolt = <1275000 1275000 1300000>;
+ opp-hz = /bits/ 64 <333500000>;
+ opp-supported-hw = <0x0001>;
+ required-opps = <&core_opp_1275>;
+ };
+
+ opp@333500000,1200 {
+ opp-microvolt = <1200000 1200000 1300000>;
+ opp-hz = /bits/ 64 <333500000>;
+ opp-supported-hw = <0x0002>;
+ required-opps = <&core_opp_1200>;
+ };
+
+ opp@351500000,1100 {
+ opp-microvolt = <1100000 1100000 1300000>;
+ opp-hz = /bits/ 64 <351500000>;
+ opp-supported-hw = <0x0008>;
+ required-opps = <&core_opp_1100>;
+ };
+
+ opp@361000000,1275 {
+ opp-microvolt = <1275000 1275000 1300000>;
+ opp-hz = /bits/ 64 <361000000>;
+ opp-supported-hw = <0x0002>;
+ required-opps = <&core_opp_1275>;
+ };
+
+ opp@380000000,1200 {
+ opp-microvolt = <1200000 1200000 1300000>;
+ opp-hz = /bits/ 64 <380000000>;
+ opp-supported-hw = <0x0004>;
+ required-opps = <&core_opp_1200>;
+ };
+
+ opp@400000000,1275 {
+ opp-microvolt = <1275000 1275000 1300000>;
+ opp-hz = /bits/ 64 <400000000>;
+ opp-supported-hw = <0x0004>;
+ required-opps = <&core_opp_1275>;
+ };
+
+ opp@400000000,1200 {
+ opp-microvolt = <1200000 1200000 1300000>;
+ opp-hz = /bits/ 64 <400000000>;
+ opp-supported-hw = <0x0008>;
+ required-opps = <&core_opp_1200>;
+ };
+ };
+
+ disp1_dvfs_opp_table: disp1-opp-table {
+ compatible = "operating-points-v2";
+
+ opp@158000000,950 {
+ opp-microvolt = <950000 950000 1300000>;
+ opp-hz = /bits/ 64 <158000000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_950>;
+ };
+
+ opp@190000000,1100 {
+ opp-microvolt = <1100000 1100000 1300000>;
+ opp-hz = /bits/ 64 <190000000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_1100>;
+ };
+ };
+
+ disp2_dvfs_opp_table: disp2-opp-table {
+ compatible = "operating-points-v2";
+
+ opp@158000000,950 {
+ opp-microvolt = <950000 950000 1300000>;
+ opp-hz = /bits/ 64 <158000000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_950>;
+ };
+
+ opp@190000000,1100 {
+ opp-microvolt = <1100000 1100000 1300000>;
+ opp-hz = /bits/ 64 <190000000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_1100>;
+ };
+ };
+
+ dsi_dvfs_opp_table: dsi-opp-table {
+ compatible = "operating-points-v2";
+
+ opp@100000000,950 {
+ opp-microvolt = <950000 950000 1300000>;
+ opp-hz = /bits/ 64 <100000000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_950>;
+ };
+
+ opp@500000000,1200 {
+ opp-microvolt = <1200000 1200000 1300000>;
+ opp-hz = /bits/ 64 <500000000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_1200>;
+ };
+ };
+
+ epp_dvfs_opp_table: epp-opp-table {
+ compatible = "operating-points-v2";
+
+ opp@133000000,950 {
+ opp-microvolt = <950000 950000 1300000>;
+ opp-hz = /bits/ 64 <133000000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_950>;
+ };
+
+ opp@171000000,1000 {
+ opp-microvolt = <1000000 1000000 1300000>;
+ opp-hz = /bits/ 64 <171000000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_1000>;
+ };
+
+ opp@247000000,1100 {
+ opp-microvolt = <1100000 1100000 1300000>;
+ opp-hz = /bits/ 64 <247000000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_1100>;
+ };
+
+ opp@300000000,1200 {
+ opp-microvolt = <1200000 1200000 1300000>;
+ opp-hz = /bits/ 64 <300000000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_1200>;
+ };
+ };
+
+ hdmi_dvfs_opp_table: hdmi-opp-table {
+ compatible = "operating-points-v2";
+
+ opp@148500000,1200 {
+ opp-microvolt = <1200000 1200000 1300000>;
+ opp-hz = /bits/ 64 <148500000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_1200>;
+ };
+ };
+
+ host1x_dvfs_opp_table: host1x-opp-table {
+ compatible = "operating-points-v2";
+
+ opp@104500000,950 {
+ opp-microvolt = <950000 950000 1300000>;
+ opp-hz = /bits/ 64 <104500000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_950>;
+ };
+
+ opp@133000000,1000 {
+ opp-microvolt = <1000000 1000000 1300000>;
+ opp-hz = /bits/ 64 <133000000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_1000>;
+ };
+
+ opp@166000000,1100 {
+ opp-microvolt = <1100000 1100000 1300000>;
+ opp-hz = /bits/ 64 <166000000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_1100>;
+ };
+ };
+
+ ide_dvfs_opp_table: ide-opp-table {
+ compatible = "operating-points-v2";
+
+ opp@100000000,1100 {
+ opp-microvolt = <1100000 1100000 1300000>;
+ opp-hz = /bits/ 64 <100000000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_1100>;
+ };
+ };
+
+ mipi_dvfs_opp_table: mipi-opp-table {
+ compatible = "operating-points-v2";
+
+ opp@40000000,1000 {
+ opp-microvolt = <1000000 1000000 1300000>;
+ opp-hz = /bits/ 64 <40000000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_1000>;
+ };
+
+ opp@60000000,1275 {
+ opp-microvolt = <1275000 1275000 1300000>;
+ opp-hz = /bits/ 64 <60000000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_1275>;
+ };
+ };
+
+ mpe_dvfs_opp_table: mpe-opp-table {
+ compatible = "operating-points-v2";
+
+ opp@104500000,950 {
+ opp-microvolt = <950000 950000 1300000>;
+ opp-hz = /bits/ 64 <104500000>;
+ opp-supported-hw = <0x0001>;
+ required-opps = <&core_opp_950>;
+ };
+
+ opp@142500000,950 {
+ opp-microvolt = <950000 950000 1300000>;
+ opp-hz = /bits/ 64 <142500000>;
+ opp-supported-hw = <0x0002>;
+ required-opps = <&core_opp_950>;
+ };
+
+ opp@152000000,1000 {
+ opp-microvolt = <1000000 1000000 1300000>;
+ opp-hz = /bits/ 64 <152000000>;
+ opp-supported-hw = <0x0001>;
+ required-opps = <&core_opp_1000>;
+ };
+
+ opp@190000000,1000 {
+ opp-microvolt = <1000000 1000000 1300000>;
+ opp-hz = /bits/ 64 <190000000>;
+ opp-supported-hw = <0x0002>;
+ required-opps = <&core_opp_1000>;
+ };
+
+ opp@190000000,950 {
+ opp-microvolt = <950000 950000 1300000>;
+ opp-hz = /bits/ 64 <190000000>;
+ opp-supported-hw = <0x0004>;
+ required-opps = <&core_opp_950>;
+ };
+
+ opp@228000000,1100 {
+ opp-microvolt = <1100000 1100000 1300000>;
+ opp-hz = /bits/ 64 <228000000>;
+ opp-supported-hw = <0x0001>;
+ required-opps = <&core_opp_1100>;
+ };
+
+ opp@228000000,950 {
+ opp-microvolt = <950000 950000 1300000>;
+ opp-hz = /bits/ 64 <228000000>;
+ opp-supported-hw = <0x0008>;
+ required-opps = <&core_opp_950>;
+ };
+
+ opp@237500000,1000 {
+ opp-microvolt = <1000000 1000000 1300000>;
+ opp-hz = /bits/ 64 <237500000>;
+ opp-supported-hw = <0x0004>;
+ required-opps = <&core_opp_1000>;
+ };
+
+ opp@266000000,1000 {
+ opp-microvolt = <1000000 1000000 1300000>;
+ opp-hz = /bits/ 64 <266000000>;
+ opp-supported-hw = <0x0008>;
+ required-opps = <&core_opp_1000>;
+ };
+
+ opp@275500000,1100 {
+ opp-microvolt = <1100000 1100000 1300000>;
+ opp-hz = /bits/ 64 <275500000>;
+ opp-supported-hw = <0x0002>;
+ required-opps = <&core_opp_1100>;
+ };
+
+ opp@300000000,1200 {
+ opp-microvolt = <1200000 1200000 1300000>;
+ opp-hz = /bits/ 64 <300000000>;
+ opp-supported-hw = <0x0003>;
+ required-opps = <&core_opp_1200>;
+ };
+
+ opp@300000000,1100 {
+ opp-microvolt = <1100000 1100000 1300000>;
+ opp-hz = /bits/ 64 <300000000>;
+ opp-supported-hw = <0x000C>;
+ required-opps = <&core_opp_1100>;
+ };
+ };
+
+ ndflash_dvfs_opp_table: ndflash-opp-table {
+ compatible = "operating-points-v2";
+
+ opp@130000000,950 {
+ opp-microvolt = <950000 950000 1300000>;
+ opp-hz = /bits/ 64 <130000000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_950>;
+ };
+
+ opp@150000000,1000 {
+ opp-microvolt = <1000000 1000000 1300000>;
+ opp-hz = /bits/ 64 <150000000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_1000>;
+ };
+
+ opp@158000000,1100 {
+ opp-microvolt = <1100000 1100000 1300000>;
+ opp-hz = /bits/ 64 <158000000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_1100>;
+ };
+
+ opp@164000000,1200 {
+ opp-microvolt = <1200000 1200000 1300000>;
+ opp-hz = /bits/ 64 <164000000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_1200>;
+ };
+ };
+
+ nor_dvfs_opp_table: nor-opp-table {
+ compatible = "operating-points-v2";
+
+ opp@92000000,1000 {
+ opp-microvolt = <1000000 1000000 1300000>;
+ opp-hz = /bits/ 64 <92000000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_1000>;
+ };
+ };
+
+ pcie_dvfs_opp_table: pcie-opp-table {
+ compatible = "operating-points-v2";
+
+ opp@250000000,1200 {
+ opp-microvolt = <1200000 1200000 1300000>;
+ opp-hz = /bits/ 64 <250000000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_1200>;
+ };
+ };
+
+ sdmmc1_dvfs_opp_table: sdmmc1-opp-table {
+ compatible = "operating-points-v2";
+
+ opp@44000000,950 {
+ opp-microvolt = <950000 950000 1300000>;
+ opp-hz = /bits/ 64 <44000000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_950>;
+ };
+
+ opp@52000000,1000 {
+ opp-microvolt = <1000000 1000000 1300000>;
+ opp-hz = /bits/ 64 <52000000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_1000>;
+ };
+ };
+
+ sdmmc2_dvfs_opp_table: sdmmc2-opp-table {
+ compatible = "operating-points-v2";
+
+ opp@44000000,950 {
+ opp-microvolt = <950000 950000 1300000>;
+ opp-hz = /bits/ 64 <44000000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_950>;
+ };
+
+ opp@52000000,1000 {
+ opp-microvolt = <1000000 1000000 1300000>;
+ opp-hz = /bits/ 64 <52000000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_1000>;
+ };
+ };
+
+ sdmmc3_dvfs_opp_table: sdmmc3-opp-table {
+ compatible = "operating-points-v2";
+
+ opp@44000000,950 {
+ opp-microvolt = <950000 950000 1300000>;
+ opp-hz = /bits/ 64 <44000000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_950>;
+ };
+
+ opp@52000000,1000 {
+ opp-microvolt = <1000000 1000000 1300000>;
+ opp-hz = /bits/ 64 <52000000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_1000>;
+ };
+ };
+
+ sdmmc4_dvfs_opp_table: sdmmc4-opp-table {
+ compatible = "operating-points-v2";
+
+ opp@44000000,950 {
+ opp-microvolt = <950000 950000 1300000>;
+ opp-hz = /bits/ 64 <44000000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_950>;
+ };
+
+ opp@52000000,1000 {
+ opp-microvolt = <1000000 1000000 1300000>;
+ opp-hz = /bits/ 64 <52000000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_1000>;
+ };
+ };
+
+ sclk_dvfs_opp_table: sclk-opp-table {
+ compatible = "operating-points-v2";
+
+ opp@95000000,950 {
+ opp-microvolt = <950000 950000 1300000>;
+ opp-hz = /bits/ 64 <95000000>;
+ opp-supported-hw = <0x0001>;
+ required-opps = <&core_opp_950>;
+ };
+
+ opp@123500000,950 {
+ opp-microvolt = <950000 950000 1300000>;
+ opp-hz = /bits/ 64 <123500000>;
+ opp-supported-hw = <0x0002>;
+ required-opps = <&core_opp_950>;
+ };
+
+ opp@133000000,1000 {
+ opp-microvolt = <1000000 1000000 1300000>;
+ opp-hz = /bits/ 64 <133000000>;
+ opp-supported-hw = <0x0001>;
+ required-opps = <&core_opp_1000>;
+ };
+
+ opp@152000000,950 {
+ opp-microvolt = <950000 950000 1300000>;
+ opp-hz = /bits/ 64 <152000000>;
+ opp-supported-hw = <0x0004>;
+ required-opps = <&core_opp_950>;
+ };
+
+ opp@159500000,1000 {
+ opp-microvolt = <1000000 1000000 1300000>;
+ opp-hz = /bits/ 64 <159500000>;
+ opp-supported-hw = <0x0002>;
+ required-opps = <&core_opp_1000>;
+ };
+
+ opp@171000000,950 {
+ opp-microvolt = <950000 950000 1300000>;
+ opp-hz = /bits/ 64 <171000000>;
+ opp-supported-hw = <0x0008>;
+ required-opps = <&core_opp_950>;
+ };
+
+ opp@180500000,1000 {
+ opp-microvolt = <1000000 1000000 1300000>;
+ opp-hz = /bits/ 64 <180500000>;
+ opp-supported-hw = <0x0004>;
+ required-opps = <&core_opp_1000>;
+ };
+
+ opp@190000000,1100 {
+ opp-microvolt = <1100000 1100000 1300000>;
+ opp-hz = /bits/ 64 <190000000>;
+ opp-supported-hw = <0x0001>;
+ required-opps = <&core_opp_1100>;
+ };
+
+ opp@207000000,1100 {
+ opp-microvolt = <1100000 1100000 1300000>;
+ opp-hz = /bits/ 64 <207000000>;
+ opp-supported-hw = <0x0002>;
+ required-opps = <&core_opp_1100>;
+ };
+
+ opp@218500000,1000 {
+ opp-microvolt = <1000000 1000000 1300000>;
+ opp-hz = /bits/ 64 <218500000>;
+ opp-supported-hw = <0x0008>;
+ required-opps = <&core_opp_1000>;
+ };
+
+ opp@222500000,1200 {
+ opp-microvolt = <1200000 1200000 1300000>;
+ opp-hz = /bits/ 64 <222500000>;
+ opp-supported-hw = <0x0001>;
+ required-opps = <&core_opp_1200>;
+ };
+
+ opp@229500000,1100 {
+ opp-microvolt = <1100000 1100000 1300000>;
+ opp-hz = /bits/ 64 <229500000>;
+ opp-supported-hw = <0x0004>;
+ required-opps = <&core_opp_1100>;
+ };
+
+ opp@240000000,1225 {
+ opp-microvolt = <1225000 1225000 1300000>;
+ opp-hz = /bits/ 64 <240000000>;
+ opp-supported-hw = <0x0001>;
+ required-opps = <&core_opp_1225>;
+ };
+
+ opp@240000000,1200 {
+ opp-microvolt = <1200000 1200000 1300000>;
+ opp-hz = /bits/ 64 <240000000>;
+ opp-supported-hw = <0x0002>;
+ required-opps = <&core_opp_1200>;
+ };
+
+ opp@247000000,1275 {
+ opp-microvolt = <1275000 1275000 1300000>;
+ opp-hz = /bits/ 64 <247000000>;
+ opp-supported-hw = <0x0001>;
+ required-opps = <&core_opp_1275>;
+ };
+
+ opp@256500000,1100 {
+ opp-microvolt = <1100000 1100000 1300000>;
+ opp-hz = /bits/ 64 <256500000>;
+ opp-supported-hw = <0x0008>;
+ required-opps = <&core_opp_1100>;
+ };
+
+ opp@260000000,1200 {
+ opp-microvolt = <1200000 1200000 1300000>;
+ opp-hz = /bits/ 64 <260000000>;
+ opp-supported-hw = <0x0004>;
+ required-opps = <&core_opp_1200>;
+ };
+
+ opp@262000000,1300 {
+ opp-microvolt = <1300000 1300000 1300000>;
+ opp-hz = /bits/ 64 <262000000>;
+ opp-supported-hw = <0x0001>;
+ required-opps = <&core_opp_1300>;
+ };
+
+ opp@264000000,1275 {
+ opp-microvolt = <1275000 1275000 1300000>;
+ opp-hz = /bits/ 64 <264000000>;
+ opp-supported-hw = <0x0002>;
+ required-opps = <&core_opp_1275>;
+ };
+
+ opp@277500000,1300 {
+ opp-microvolt = <1300000 1300000 1300000>;
+ opp-hz = /bits/ 64 <277500000>;
+ opp-supported-hw = <0x0002>;
+ required-opps = <&core_opp_1300>;
+ };
+
+ opp@285000000,1275 {
+ opp-microvolt = <1275000 1275000 1300000>;
+ opp-hz = /bits/ 64 <285000000>;
+ opp-supported-hw = <0x0004>;
+ required-opps = <&core_opp_1275>;
+ };
+
+ opp@292500000,1200 {
+ opp-microvolt = <1200000 1200000 1300000>;
+ opp-hz = /bits/ 64 <292500000>;
+ opp-supported-hw = <0x0008>;
+ required-opps = <&core_opp_1200>;
+ };
+
+ opp@300000000,1300 {
+ opp-microvolt = <1300000 1300000 1300000>;
+ opp-hz = /bits/ 64 <300000000>;
+ opp-supported-hw = <0x0004>;
+ required-opps = <&core_opp_1300>;
+ };
+
+ opp@300000000,1275 {
+ opp-microvolt = <1275000 1275000 1300000>;
+ opp-hz = /bits/ 64 <300000000>;
+ opp-supported-hw = <0x0008>;
+ required-opps = <&core_opp_1275>;
+ };
+ };
+
+ tvo_dvfs_opp_table: tvo-opp-table {
+ compatible = "operating-points-v2";
+
+ opp@250000000,1200 {
+ opp-microvolt = <1200000 1200000 1300000>;
+ opp-hz = /bits/ 64 <250000000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_1200>;
+ };
+ };
+
+ usbd_dvfs_opp_table: usbd-opp-table {
+ compatible = "operating-points-v2";
+
+ opp@480000000,1100 {
+ opp-microvolt = <1100000 1100000 1300000>;
+ opp-hz = /bits/ 64 <480000000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_1100>;
+ };
+ };
+
+ usb2_dvfs_opp_table: usb2-opp-table {
+ compatible = "operating-points-v2";
+
+ opp@480000000,1100 {
+ opp-microvolt = <1100000 1100000 1300000>;
+ opp-hz = /bits/ 64 <480000000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_1100>;
+ };
+ };
+
+ usb3_dvfs_opp_table: usb3-opp-table {
+ compatible = "operating-points-v2";
+
+ opp@480000000,1100 {
+ opp-microvolt = <1100000 1100000 1300000>;
+ opp-hz = /bits/ 64 <480000000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_1100>;
+ };
+ };
+
+ vde_dvfs_opp_table: vde-opp-table {
+ compatible = "operating-points-v2";
+
+ opp@95000000,950 {
+ opp-microvolt = <950000 950000 1300000>;
+ opp-hz = /bits/ 64 <95000000>;
+ opp-supported-hw = <0x0001>;
+ required-opps = <&core_opp_950>;
+ };
+
+ opp@123500000,1000 {
+ opp-microvolt = <1000000 1000000 1300000>;
+ opp-hz = /bits/ 64 <123500000>;
+ opp-supported-hw = <0x0001>;
+ required-opps = <&core_opp_1000>;
+ };
+
+ opp@123500000,950 {
+ opp-microvolt = <950000 950000 1300000>;
+ opp-hz = /bits/ 64 <123500000>;
+ opp-supported-hw = <0x0002>;
+ required-opps = <&core_opp_950>;
+ };
+
+ opp@152000000,1000 {
+ opp-microvolt = <1000000 1000000 1300000>;
+ opp-hz = /bits/ 64 <152000000>;
+ opp-supported-hw = <0x0002>;
+ required-opps = <&core_opp_1000>;
+ };
+
+ opp@152000000,950 {
+ opp-microvolt = <950000 950000 1300000>;
+ opp-hz = /bits/ 64 <152000000>;
+ opp-supported-hw = <0x0004>;
+ required-opps = <&core_opp_950>;
+ };
+
+ opp@171000000,950 {
+ opp-microvolt = <950000 950000 1300000>;
+ opp-hz = /bits/ 64 <171000000>;
+ opp-supported-hw = <0x0008>;
+ required-opps = <&core_opp_950>;
+ };
+
+ opp@209000000,1100 {
+ opp-microvolt = <1100000 1100000 1300000>;
+ opp-hz = /bits/ 64 <209000000>;
+ opp-supported-hw = <0x0001>;
+ required-opps = <&core_opp_1100>;
+ };
+
+ opp@209000000,1000 {
+ opp-microvolt = <1000000 1000000 1300000>;
+ opp-hz = /bits/ 64 <209000000>;
+ opp-supported-hw = <0x0004>;
+ required-opps = <&core_opp_1000>;
+ };
+
+ opp@218500000,1000 {
+ opp-microvolt = <1000000 1000000 1300000>;
+ opp-hz = /bits/ 64 <218500000>;
+ opp-supported-hw = <0x0008>;
+ required-opps = <&core_opp_1000>;
+ };
+
+ opp@237500000,1100 {
+ opp-microvolt = <1100000 1100000 1300000>;
+ opp-hz = /bits/ 64 <237500000>;
+ opp-supported-hw = <0x0002>;
+ required-opps = <&core_opp_1100>;
+ };
+
+ opp@275500000,1200 {
+ opp-microvolt = <1200000 1200000 1300000>;
+ opp-hz = /bits/ 64 <275500000>;
+ opp-supported-hw = <0x0001>;
+ required-opps = <&core_opp_1200>;
+ };
+
+ opp@285000000,1100 {
+ opp-microvolt = <1100000 1100000 1300000>;
+ opp-hz = /bits/ 64 <285000000>;
+ opp-supported-hw = <0x0004>;
+ required-opps = <&core_opp_1100>;
+ };
+
+ opp@300000000,1275 {
+ opp-microvolt = <1275000 1275000 1300000>;
+ opp-hz = /bits/ 64 <300000000>;
+ opp-supported-hw = <0x0001>;
+ required-opps = <&core_opp_1275>;
+ };
+
+ opp@300000000,1200 {
+ opp-microvolt = <1200000 1200000 1300000>;
+ opp-hz = /bits/ 64 <300000000>;
+ opp-supported-hw = <0x0006>;
+ required-opps = <&core_opp_1200>;
+ };
+
+ opp@300000000,1100 {
+ opp-microvolt = <1100000 1100000 1300000>;
+ opp-hz = /bits/ 64 <300000000>;
+ opp-supported-hw = <0x0008>;
+ required-opps = <&core_opp_1100>;
+ };
+ };
+
+ vi_dvfs_opp_table: vi-opp-table {
+ compatible = "operating-points-v2";
+
+ opp@85000000,950 {
+ opp-microvolt = <950000 950000 1300000>;
+ opp-hz = /bits/ 64 <85000000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_950>;
+ };
+
+ opp@100000000,1000 {
+ opp-microvolt = <1000000 1000000 1300000>;
+ opp-hz = /bits/ 64 <100000000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_1000>;
+ };
+
+ opp@150000000,1100 {
+ opp-microvolt = <1100000 1100000 1300000>;
+ opp-hz = /bits/ 64 <150000000>;
+ opp-supported-hw = <0x000F>;
+ required-opps = <&core_opp_1100>;
};
};
};
diff --git a/arch/arm/boot/dts/tegra20-seaboard.dts b/arch/arm/boot/dts/tegra20-seaboard.dts
index 92d494b8c3d2..5aeb7bb6c415 100644
--- a/arch/arm/boot/dts/tegra20-seaboard.dts
+++ b/arch/arm/boot/dts/tegra20-seaboard.dts
@@ -444,7 +444,7 @@ sys_reg: sys {
regulator-always-on;
};

- sm0 {
+ vdd_core: sm0 {
regulator-name = "vdd_sm0,vdd_core";
regulator-min-microvolt = <1300000>;
regulator-max-microvolt = <1300000>;
@@ -689,6 +689,7 @@ pmc@7000e400 {
nvidia,core-pwr-good-time = <3845 3845>;
nvidia,core-pwr-off-time = <3875>;
nvidia,sys-clock-req-active-high;
+ core-supply = <&vdd_core>;
};

memory-controller@7000f400 {
diff --git a/arch/arm/boot/dts/tegra20-tamonten.dtsi b/arch/arm/boot/dts/tegra20-tamonten.dtsi
index dd4d506683de..d5888d958175 100644
--- a/arch/arm/boot/dts/tegra20-tamonten.dtsi
+++ b/arch/arm/boot/dts/tegra20-tamonten.dtsi
@@ -357,7 +357,7 @@ sys_reg: sys {
regulator-always-on;
};

- sm0 {
+ vdd_core: sm0 {
regulator-name = "vdd_sys_sm0,vdd_core";
regulator-min-microvolt = <1200000>;
regulator-max-microvolt = <1200000>;
@@ -477,6 +477,7 @@ pmc@7000e400 {
nvidia,core-pwr-good-time = <3845 3845>;
nvidia,core-pwr-off-time = <3875>;
nvidia,sys-clock-req-active-high;
+ core-supply = <&vdd_core>;
};

pcie@80003000 {
diff --git a/arch/arm/boot/dts/tegra20-trimslice.dts b/arch/arm/boot/dts/tegra20-trimslice.dts
index 4bc87bc0c2a4..582dc7910ff8 100644
--- a/arch/arm/boot/dts/tegra20-trimslice.dts
+++ b/arch/arm/boot/dts/tegra20-trimslice.dts
@@ -321,6 +321,7 @@ pmc@7000e400 {
nvidia,core-pwr-good-time = <3845 3845>;
nvidia,core-pwr-off-time = <3875>;
nvidia,sys-clock-req-active-high;
+ core-supply = <&vdd_core>;
};

pcie@80003000 {
@@ -444,6 +445,14 @@ pci_vdd_reg: regulator@4 {
regulator-always-on;
};

+ vdd_core: regulator@5 {
+ compatible = "regulator-fixed";
+ regulator-name = "vdd_core";
+ regulator-min-microvolt = <1300000>;
+ regulator-max-microvolt = <1300000>;
+ regulator-always-on;
+ };
+
sound {
compatible = "nvidia,tegra-audio-trimslice";
nvidia,i2s-controller = <&tegra_i2s1>;
diff --git a/arch/arm/boot/dts/tegra20-ventana.dts b/arch/arm/boot/dts/tegra20-ventana.dts
index 5a2578b3707f..e41ce1b1ec3f 100644
--- a/arch/arm/boot/dts/tegra20-ventana.dts
+++ b/arch/arm/boot/dts/tegra20-ventana.dts
@@ -544,6 +544,7 @@ pmc@7000e400 {
nvidia,core-pwr-good-time = <3845 3845>;
nvidia,core-pwr-off-time = <458>;
nvidia,sys-clock-req-active-high;
+ core-supply = <&vdd_core>;
};

usb@c5000000 {
diff --git a/arch/arm/boot/dts/tegra20.dtsi b/arch/arm/boot/dts/tegra20.dtsi
index 6ce498178105..ce38055a8952 100644
--- a/arch/arm/boot/dts/tegra20.dtsi
+++ b/arch/arm/boot/dts/tegra20.dtsi
@@ -42,6 +42,8 @@ host1x@50000000 {
clock-names = "host1x";
resets = <&tegra_car 28>;
reset-names = "host1x";
+ operating-points-v2 = <&host1x_dvfs_opp_table>;
+ power-domains = <&pd_core>;

#address-cells = <1>;
#size-cells = <1>;
@@ -55,6 +57,8 @@ mpe@54040000 {
clocks = <&tegra_car TEGRA20_CLK_MPE>;
resets = <&tegra_car 60>;
reset-names = "mpe";
+ operating-points-v2 = <&mpe_dvfs_opp_table>;
+ power-domains = <&pd_mpe>;
};

vi@54080000 {
@@ -64,6 +68,8 @@ vi@54080000 {
clocks = <&tegra_car TEGRA20_CLK_VI>;
resets = <&tegra_car 20>;
reset-names = "vi";
+ operating-points-v2 = <&vi_dvfs_opp_table>;
+ power-domains = <&pd_venc>;
};

epp@540c0000 {
@@ -73,6 +79,8 @@ epp@540c0000 {
clocks = <&tegra_car TEGRA20_CLK_EPP>;
resets = <&tegra_car 19>;
reset-names = "epp";
+ operating-points-v2 = <&epp_dvfs_opp_table>;
+ power-domains = <&pd_core>;
};

isp@54100000 {
@@ -82,6 +90,7 @@ isp@54100000 {
clocks = <&tegra_car TEGRA20_CLK_ISP>;
resets = <&tegra_car 23>;
reset-names = "isp";
+ power-domains = <&pd_venc>;
};

gr2d@54140000 {
@@ -91,6 +100,8 @@ gr2d@54140000 {
clocks = <&tegra_car TEGRA20_CLK_GR2D>;
resets = <&tegra_car 21>;
reset-names = "2d";
+ operating-points-v2 = <&gr2d_dvfs_opp_table>;
+ power-domains = <&pd_core>;
};

gr3d@54180000 {
@@ -99,6 +110,8 @@ gr3d@54180000 {
clocks = <&tegra_car TEGRA20_CLK_GR3D>;
resets = <&tegra_car 24>;
reset-names = "3d";
+ operating-points-v2 = <&gr3d_dvfs_opp_table>;
+ power-domains = <&pd_3d>;
};

dc@54200000 {
@@ -110,6 +123,8 @@ dc@54200000 {
clock-names = "dc", "parent";
resets = <&tegra_car 27>;
reset-names = "dc";
+ operating-points-v2 = <&disp1_dvfs_opp_table>;
+ power-domains = <&pd_core>;

nvidia,head = <0>;

@@ -138,6 +153,8 @@ dc@54240000 {
clock-names = "dc", "parent";
resets = <&tegra_car 26>;
reset-names = "dc";
+ operating-points-v2 = <&disp2_dvfs_opp_table>;
+ power-domains = <&pd_core>;

nvidia,head = <1>;

@@ -166,6 +183,8 @@ hdmi@54280000 {
clock-names = "hdmi", "parent";
resets = <&tegra_car 51>;
reset-names = "hdmi";
+ operating-points-v2 = <&hdmi_dvfs_opp_table>;
+ power-domains = <&pd_core>;
status = "disabled";
};

@@ -174,6 +193,8 @@ tvo@542c0000 {
reg = <0x542c0000 0x00040000>;
interrupts = <GIC_SPI 76 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&tegra_car TEGRA20_CLK_TVO>;
+ operating-points-v2 = <&tvo_dvfs_opp_table>;
+ power-domains = <&pd_core>;
status = "disabled";
};

@@ -185,6 +206,8 @@ dsi@54300000 {
clock-names = "dsi", "parent";
resets = <&tegra_car 48>;
reset-names = "dsi";
+ operating-points-v2 = <&dsi_dvfs_opp_table>;
+ power-domains = <&pd_core>;
status = "disabled";
};
};
@@ -242,6 +265,13 @@ tegra_car: clock@60006000 {
reg = <0x60006000 0x1000>;
#clock-cells = <1>;
#reset-cells = <1>;
+
+ sclk {
+ compatible = "nvidia,tegra20-sclk";
+ operating-points-v2 = <&sclk_dvfs_opp_table>;
+ clocks = <&tegra_car TEGRA20_CLK_SCLK>;
+ power-domains = <&pd_core>;
+ };
};

flow-controller@60007000 {
@@ -319,6 +349,8 @@ vde@6001a000 {
clocks = <&tegra_car TEGRA20_CLK_VDE>;
reset-names = "vde", "mc";
resets = <&tegra_car 61>, <&mc TEGRA20_MC_RESET_VDE>;
+ operating-points-v2 = <&vde_dvfs_opp_table>;
+ power-domains = <&pd_vde>;
};

apbmisc@70000800 {
@@ -460,6 +492,8 @@ nand-controller@70008000 {
reset-names = "nand";
assigned-clocks = <&tegra_car TEGRA20_CLK_NDFLASH>;
assigned-clock-rates = <150000000>;
+ operating-points-v2 = <&ndflash_dvfs_opp_table>;
+ power-domains = <&pd_core>;
status = "disabled";
};

@@ -473,6 +507,8 @@ gmi@70009000 {
clock-names = "gmi";
resets = <&tegra_car 42>;
reset-names = "gmi";
+ operating-points-v2 = <&nor_dvfs_opp_table>;
+ power-domains = <&pd_core>;
status = "disabled";
};

@@ -643,6 +679,52 @@ tegra_pmc: pmc@7000e400 {
clocks = <&tegra_car TEGRA20_CLK_PCLK>, <&clk32k_in>;
clock-names = "pclk", "clk32k_in";
#clock-cells = <1>;
+
+ pd_core: core-domain {
+ operating-points-v2 = <&core_opp_table>;
+ #power-domain-cells = <0>;
+ };
+
+ powergates {
+ pd_3d: td {
+ clocks = <&tegra_car TEGRA20_CLK_GR3D>;
+ resets = <&mc TEGRA20_MC_RESET_3D>,
+ <&tegra_car TEGRA20_CLK_GR3D>;
+ power-domains = <&pd_core>;
+ #power-domain-cells = <0>;
+ };
+
+ pd_venc: venc {
+ clocks = <&tegra_car TEGRA20_CLK_ISP>,
+ <&tegra_car TEGRA20_CLK_VI>,
+ <&tegra_car TEGRA20_CLK_CSI>;
+ resets = <&mc TEGRA20_MC_RESET_ISP>,
+ <&mc TEGRA20_MC_RESET_VI>,
+ <&tegra_car TEGRA20_CLK_ISP>,
+ <&tegra_car 20 /* VI */>,
+ <&tegra_car TEGRA20_CLK_CSI>;
+ power-domains = <&pd_core>;
+ #power-domain-cells = <0>;
+ };
+
+ pd_vde: vdec {
+ clocks = <&tegra_car TEGRA20_CLK_VDE>;
+ resets = <&mc TEGRA20_MC_RESET_VDE>,
+ <&tegra_car TEGRA20_CLK_VDE>;
+ power-domains = <&pd_core>;
+ #power-domain-cells = <0>;
+ };
+
+ pd_mpe: mpe {
+ clocks = <&tegra_car TEGRA20_CLK_MPE>;
+ resets = <&mc TEGRA20_MC_RESET_MPEA>,
+ <&mc TEGRA20_MC_RESET_MPEB>,
+ <&mc TEGRA20_MC_RESET_MPEC>,
+ <&tegra_car TEGRA20_CLK_MPE>;
+ power-domains = <&pd_core>;
+ #power-domain-cells = <0>;
+ };
+ };
};

mc: memory-controller@7000f000 {
@@ -662,6 +744,7 @@ emc: memory-controller@7000f400 {
reg = <0x7000f400 0x400>;
interrupts = <GIC_SPI 78 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&tegra_car TEGRA20_CLK_EMC>;
+ power-domains = <&pd_core>;
#address-cells = <1>;
#size-cells = <0>;
#interconnect-cells = <0>;
@@ -712,6 +795,9 @@ pcie@80003000 {
<&tegra_car 72>,
<&tegra_car 74>;
reset-names = "pex", "afi", "pcie_x";
+ operating-points-v2 = <&pcie_dvfs_opp_table>;
+ power-domains = <&pd_core>;
+
status = "disabled";

pci@1,0 {
@@ -754,6 +840,8 @@ usb@c5000000 {
reset-names = "usb";
nvidia,needs-double-reset;
nvidia,phy = <&phy1>;
+ operating-points-v2 = <&usbd_dvfs_opp_table>;
+ power-domains = <&pd_core>;
status = "disabled";
};

@@ -791,6 +879,8 @@ usb@c5004000 {
resets = <&tegra_car 58>;
reset-names = "usb";
nvidia,phy = <&phy2>;
+ operating-points-v2 = <&usb2_dvfs_opp_table>;
+ power-domains = <&pd_core>;
status = "disabled";
};

@@ -817,6 +907,8 @@ usb@c5008000 {
resets = <&tegra_car 59>;
reset-names = "usb";
nvidia,phy = <&phy3>;
+ operating-points-v2 = <&usb3_dvfs_opp_table>;
+ power-domains = <&pd_core>;
status = "disabled";
};

@@ -851,6 +943,8 @@ mmc@c8000000 {
clock-names = "sdhci";
resets = <&tegra_car 14>;
reset-names = "sdhci";
+ operating-points-v2 = <&sdmmc1_dvfs_opp_table>;
+ power-domains = <&pd_core>;
status = "disabled";
};

@@ -862,6 +956,8 @@ mmc@c8000200 {
clock-names = "sdhci";
resets = <&tegra_car 9>;
reset-names = "sdhci";
+ operating-points-v2 = <&sdmmc2_dvfs_opp_table>;
+ power-domains = <&pd_core>;
status = "disabled";
};

@@ -873,6 +969,8 @@ mmc@c8000400 {
clock-names = "sdhci";
resets = <&tegra_car 69>;
reset-names = "sdhci";
+ operating-points-v2 = <&sdmmc3_dvfs_opp_table>;
+ power-domains = <&pd_core>;
status = "disabled";
};

@@ -884,6 +982,8 @@ mmc@c8000600 {
clock-names = "sdhci";
resets = <&tegra_car 15>;
reset-names = "sdhci";
+ operating-points-v2 = <&sdmmc4_dvfs_opp_table>;
+ power-domains = <&pd_core>;
status = "disabled";
};

--
2.32.0

2021-09-26 22:47:31

by Dmitry Osipenko

[permalink] [raw]
Subject: [PATCH v13 33/35] ARM: tegra: Add Memory Client resets to Tegra20 GR2D, GR3D and Host1x

Memory access must be blocked before hardware reset is asserted and before
power is gated, otherwise a serious hardware fault is inevitable. Add
reset for memory clients to the GR2D, GR3D and Host1x nodes.

Tested-by: Paul Fertser <[email protected]> # PAZ00 T20
Tested-by: Nicolas Chauvet <[email protected]> # PAZ00 T20
Signed-off-by: Dmitry Osipenko <[email protected]>
---
arch/arm/boot/dts/tegra20.dtsi | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/arch/arm/boot/dts/tegra20.dtsi b/arch/arm/boot/dts/tegra20.dtsi
index ce38055a8952..eb6e5518fb5f 100644
--- a/arch/arm/boot/dts/tegra20.dtsi
+++ b/arch/arm/boot/dts/tegra20.dtsi
@@ -40,8 +40,8 @@ host1x@50000000 {
interrupt-names = "syncpt", "host1x";
clocks = <&tegra_car TEGRA20_CLK_HOST1X>;
clock-names = "host1x";
- resets = <&tegra_car 28>;
- reset-names = "host1x";
+ resets = <&tegra_car 28>, <&mc TEGRA20_MC_RESET_HC>;
+ reset-names = "host1x", "mc";
operating-points-v2 = <&host1x_dvfs_opp_table>;
power-domains = <&pd_core>;

@@ -98,8 +98,8 @@ gr2d@54140000 {
reg = <0x54140000 0x00040000>;
interrupts = <GIC_SPI 72 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&tegra_car TEGRA20_CLK_GR2D>;
- resets = <&tegra_car 21>;
- reset-names = "2d";
+ resets = <&tegra_car 21>, <&mc TEGRA20_MC_RESET_2D>;
+ reset-names = "2d", "mc";
operating-points-v2 = <&gr2d_dvfs_opp_table>;
power-domains = <&pd_core>;
};
@@ -108,8 +108,8 @@ gr3d@54180000 {
compatible = "nvidia,tegra20-gr3d";
reg = <0x54180000 0x00040000>;
clocks = <&tegra_car TEGRA20_CLK_GR3D>;
- resets = <&tegra_car 24>;
- reset-names = "3d";
+ resets = <&tegra_car 24>, <&mc TEGRA20_MC_RESET_3D>;
+ reset-names = "3d", "mc";
operating-points-v2 = <&gr3d_dvfs_opp_table>;
power-domains = <&pd_3d>;
};
--
2.32.0

2021-09-26 22:47:59

by Dmitry Osipenko

[permalink] [raw]
Subject: [PATCH v13 35/35] ARM: tegra20/30: Disable unused host1x hardware

MPE, VI, EPP and ISP were never used and we don't have drivers for them.
Since these modules are enabled by default in a device-tree, a device is
created for them, blocking voltage scaling because there is no driver to
bind, and thus, state of PMC driver is never synced. Disable them.

Signed-off-by: Dmitry Osipenko <[email protected]>
---
arch/arm/boot/dts/tegra20.dtsi | 4 ++++
arch/arm/boot/dts/tegra30.dtsi | 8 ++++++++
2 files changed, 12 insertions(+)

diff --git a/arch/arm/boot/dts/tegra20.dtsi b/arch/arm/boot/dts/tegra20.dtsi
index eb6e5518fb5f..05788c9cddc1 100644
--- a/arch/arm/boot/dts/tegra20.dtsi
+++ b/arch/arm/boot/dts/tegra20.dtsi
@@ -59,6 +59,7 @@ mpe@54040000 {
reset-names = "mpe";
operating-points-v2 = <&mpe_dvfs_opp_table>;
power-domains = <&pd_mpe>;
+ status = "disabled";
};

vi@54080000 {
@@ -70,6 +71,7 @@ vi@54080000 {
reset-names = "vi";
operating-points-v2 = <&vi_dvfs_opp_table>;
power-domains = <&pd_venc>;
+ status = "disabled";
};

epp@540c0000 {
@@ -81,6 +83,7 @@ epp@540c0000 {
reset-names = "epp";
operating-points-v2 = <&epp_dvfs_opp_table>;
power-domains = <&pd_core>;
+ status = "disabled";
};

isp@54100000 {
@@ -91,6 +94,7 @@ isp@54100000 {
resets = <&tegra_car 23>;
reset-names = "isp";
power-domains = <&pd_venc>;
+ status = "disabled";
};

gr2d@54140000 {
diff --git a/arch/arm/boot/dts/tegra30.dtsi b/arch/arm/boot/dts/tegra30.dtsi
index 09cb1ab75312..f9613384a487 100644
--- a/arch/arm/boot/dts/tegra30.dtsi
+++ b/arch/arm/boot/dts/tegra30.dtsi
@@ -145,6 +145,8 @@ mpe@54040000 {
power-domains = <&pd_mpe>;

iommus = <&mc TEGRA_SWGROUP_MPE>;
+
+ status = "disabled";
};

vi@54080000 {
@@ -158,6 +160,8 @@ vi@54080000 {
power-domains = <&pd_venc>;

iommus = <&mc TEGRA_SWGROUP_VI>;
+
+ status = "disabled";
};

epp@540c0000 {
@@ -171,6 +175,8 @@ epp@540c0000 {
power-domains = <&pd_heg>;

iommus = <&mc TEGRA_SWGROUP_EPP>;
+
+ status = "disabled";
};

isp@54100000 {
@@ -183,6 +189,8 @@ isp@54100000 {
power-domains = <&pd_venc>;

iommus = <&mc TEGRA_SWGROUP_ISP>;
+
+ status = "disabled";
};

gr2d@54140000 {
--
2.32.0

2021-09-30 00:43:50

by Dmitry Osipenko

[permalink] [raw]
Subject: Re: [PATCH v13 16/35] usb: chipidea: tegra: Add runtime PM and OPP support

27.09.2021 01:40, Dmitry Osipenko пишет:
> The Tegra USB controller belongs to the core power domain and we're going
> to enable GENPD support for the core domain. Now USB controller must be
> resumed using runtime PM API in order to initialize the USB power state.
> We already support runtime PM for the CI device, but CI's PM is separated
> from the RPM managed by tegra-usb driver. Add runtime PM and OPP support
> to the driver.
>
> Signed-off-by: Dmitry Osipenko <[email protected]>
> ---
> drivers/usb/chipidea/ci_hdrc_tegra.c | 53 ++++++++++++++++++++++++----
> 1 file changed, 46 insertions(+), 7 deletions(-)

Peter Chen, could you please ack this patch? Thanks in advance!

2021-09-30 14:08:54

by Peter Chen

[permalink] [raw]
Subject: Re: [PATCH v13 16/35] usb: chipidea: tegra: Add runtime PM and OPP support

On 21-09-27 01:40:39, Dmitry Osipenko wrote:
> The Tegra USB controller belongs to the core power domain and we're going
> to enable GENPD support for the core domain. Now USB controller must be
> resumed using runtime PM API in order to initialize the USB power state.
> We already support runtime PM for the CI device, but CI's PM is separated
> from the RPM managed by tegra-usb driver. Add runtime PM and OPP support
> to the driver.
>
> Signed-off-by: Dmitry Osipenko <[email protected]>
> ---
> drivers/usb/chipidea/ci_hdrc_tegra.c | 53 ++++++++++++++++++++++++----
> 1 file changed, 46 insertions(+), 7 deletions(-)
>
> diff --git a/drivers/usb/chipidea/ci_hdrc_tegra.c b/drivers/usb/chipidea/ci_hdrc_tegra.c
> index 60361141ac04..3142ef7ebe42 100644
> --- a/drivers/usb/chipidea/ci_hdrc_tegra.c
> +++ b/drivers/usb/chipidea/ci_hdrc_tegra.c
> @@ -7,6 +7,7 @@
> #include <linux/io.h>
> #include <linux/module.h>
> #include <linux/of_device.h>
> +#include <linux/pm_runtime.h>
> #include <linux/reset.h>
>
> #include <linux/usb.h>
> @@ -15,6 +16,8 @@
> #include <linux/usb/of.h>
> #include <linux/usb/phy.h>
>
> +#include <soc/tegra/common.h>
> +
> #include "../host/ehci.h"
>
> #include "ci.h"
> @@ -278,6 +281,8 @@ static int tegra_usb_probe(struct platform_device *pdev)
> if (!usb)
> return -ENOMEM;
>
> + platform_set_drvdata(pdev, usb);
> +
> soc = of_device_get_match_data(&pdev->dev);
> if (!soc) {
> dev_err(&pdev->dev, "failed to match OF data\n");
> @@ -296,11 +301,17 @@ static int tegra_usb_probe(struct platform_device *pdev)
> return err;
> }
>
> - err = clk_prepare_enable(usb->clk);
> - if (err < 0) {
> - dev_err(&pdev->dev, "failed to enable clock: %d\n", err);
> + err = devm_pm_runtime_enable(&pdev->dev);
> + if (err)
> + return err;
> +
> + err = devm_tegra_core_dev_init_opp_table_common(&pdev->dev);
> + if (err)
> + return err;
> +
> + err = pm_runtime_resume_and_get(&pdev->dev);
> + if (err)
> return err;
> - }
>
> if (device_property_present(&pdev->dev, "nvidia,needs-double-reset"))
> usb->needs_double_reset = true;
> @@ -320,8 +331,6 @@ static int tegra_usb_probe(struct platform_device *pdev)
> if (err)
> goto fail_power_off;
>
> - platform_set_drvdata(pdev, usb);
> -
> /* setup and register ChipIdea HDRC device */
> usb->soc = soc;
> usb->data.name = "tegra-usb";
> @@ -350,7 +359,8 @@ static int tegra_usb_probe(struct platform_device *pdev)
> phy_shutdown:
> usb_phy_shutdown(usb->phy);
> fail_power_off:
> - clk_disable_unprepare(usb->clk);
> + pm_runtime_put(&pdev->dev);
> +
> return err;
> }
>
> @@ -360,15 +370,44 @@ static int tegra_usb_remove(struct platform_device *pdev)
>
> ci_hdrc_remove_device(usb->dev);
> usb_phy_shutdown(usb->phy);
> + pm_runtime_put(&pdev->dev);
> +
> + return 0;
> +}
> +
> +static int __maybe_unused tegra_usb_runtime_resume(struct device *dev)
> +{
> + struct tegra_usb *usb = dev_get_drvdata(dev);
> + int err;
> +
> + err = clk_prepare_enable(usb->clk);
> + if (err < 0) {
> + dev_err(dev, "failed to enable clock: %d\n", err);
> + return err;
> + }
> +
> + return 0;
> +}
> +
> +static int __maybe_unused tegra_usb_runtime_suspend(struct device *dev)
> +{
> + struct tegra_usb *usb = dev_get_drvdata(dev);
> +
> clk_disable_unprepare(usb->clk);
>
> return 0;
> }
>
> +static const struct dev_pm_ops tegra_usb_pm = {
> + SET_RUNTIME_PM_OPS(tegra_usb_runtime_suspend, tegra_usb_runtime_resume,
> + NULL)
> +};
> +
> static struct platform_driver tegra_usb_driver = {
> .driver = {
> .name = "tegra-usb",
> .of_match_table = tegra_usb_of_match,
> + .pm = &tegra_usb_pm,
> },
> .probe = tegra_usb_probe,
> .remove = tegra_usb_remove,
> --
> 2.32.0
>

I got below compile error if only compile this file, I think previous patches
should include the definition, if that, feel free to add my ack to this
patch.

Acked-by: Peter Chen <[email protected]>

drivers/usb/chipidea/ci_hdrc_tegra.c:308:8: error: implicit declaration of function ‘devm_tegra_core_dev_init_opp_table_common’;
did you mean ‘devm_tegra_core_dev_init_opp_table’? [-Werror=implicit-function-declaration]
308 | err = devm_tegra_core_dev_init_opp_table_common(&pdev->dev);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
| devm_tegra_core_dev_init_opp_table


--

Thanks,
Peter Chen

2021-09-30 14:14:48

by Dmitry Osipenko

[permalink] [raw]
Subject: Re: [PATCH v13 16/35] usb: chipidea: tegra: Add runtime PM and OPP support

30.09.2021 17:06, Peter Chen пишет:
> On 21-09-27 01:40:39, Dmitry Osipenko wrote:
>> The Tegra USB controller belongs to the core power domain and we're going
>> to enable GENPD support for the core domain. Now USB controller must be
>> resumed using runtime PM API in order to initialize the USB power state.
>> We already support runtime PM for the CI device, but CI's PM is separated
>> from the RPM managed by tegra-usb driver. Add runtime PM and OPP support
>> to the driver.
>>
>> Signed-off-by: Dmitry Osipenko <[email protected]>
>> ---
>> drivers/usb/chipidea/ci_hdrc_tegra.c | 53 ++++++++++++++++++++++++----
>> 1 file changed, 46 insertions(+), 7 deletions(-)
...
>
> I got below compile error if only compile this file, I think previous patches
> should include the definition, if that, feel free to add my ack to this
> patch.
>
> Acked-by: Peter Chen <[email protected]>
>
> drivers/usb/chipidea/ci_hdrc_tegra.c:308:8: error: implicit declaration of function ‘devm_tegra_core_dev_init_opp_table_common’;
> did you mean ‘devm_tegra_core_dev_init_opp_table’? [-Werror=implicit-function-declaration]
> 308 | err = devm_tegra_core_dev_init_opp_table_common(&pdev->dev);
> | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> | devm_tegra_core_dev_init_opp_table

That's correct, devm_tegra_core_dev_init_opp_table_common() is added by
an earlier patch of this series. Thank you!

2021-10-01 13:11:41

by Ulf Hansson

[permalink] [raw]
Subject: Re: [PATCH v13 02/35] soc/tegra: Add devm_tegra_core_dev_init_opp_table_common()

On Mon, 27 Sept 2021 at 00:42, Dmitry Osipenko <[email protected]> wrote:
>
> Only couple drivers need to get the -ENODEV error code and majority of
> drivers need to explicitly initialize the performance state. Add new
> common helper which sets up OPP table for these drivers.
>
> Signed-off-by: Dmitry Osipenko <[email protected]>
> ---
> include/soc/tegra/common.h | 24 ++++++++++++++++++++++++
> 1 file changed, 24 insertions(+)
>
> diff --git a/include/soc/tegra/common.h b/include/soc/tegra/common.h
> index af41ad80ec21..5b4a042f60fb 100644
> --- a/include/soc/tegra/common.h
> +++ b/include/soc/tegra/common.h
> @@ -39,4 +39,28 @@ devm_tegra_core_dev_init_opp_table(struct device *dev,
> }
> #endif
>
> +/*
> + * This function should be invoked with the enabled runtime PM of the device
> + * in order to initialize performance state properly. Most of Tegra devices
> + * are assumed to be suspended at a probe time and GENPD require RPM to be
> + * enabled to set up the rpm-resume state, otherwise device is active and
> + * performance state is applied immediately. Note that it will initialize
> + * OPP bandwidth if it's wired in a device-tree for this device, which is
> + * undesirable for a suspended device.
> + */
> +static inline int
> +devm_tegra_core_dev_init_opp_table_common(struct device *dev)
> +{
> + struct tegra_core_opp_params opp_params = {};
> + int err;
> +
> + opp_params.init_state = true;
> +
> + err = devm_tegra_core_dev_init_opp_table(dev, &opp_params);
> + if (err != -ENODEV)
> + return err;
> +
> + return 0;
> +}

Just want to share a few thoughts around these functions.

So, I assume it's fine to call
devm_tegra_core_dev_init_opp_table_common() or
devm_tegra_core_dev_init_opp_table() from consumer drivers during
->probe(), as long as those drivers are tegra specific, which I assume
all are in the series!?

My point is, a cross SoC consumer driver that needs to initiate OPP
tables can get rather messy, if it would need to make one specific
function call per SoC.

That said, I hope we can tackle this as a separate/future problem, so
the series can get merged as is.

> +
> #endif /* __SOC_TEGRA_COMMON_H__ */
> --
> 2.32.0
>

Kind regards
Uffe

2021-10-01 13:28:05

by Ulf Hansson

[permalink] [raw]
Subject: Re: [PATCH v13 09/35] gpu: host1x: Add runtime PM and OPP support

On Mon, 27 Sept 2021 at 00:42, Dmitry Osipenko <[email protected]> wrote:
>
> Add runtime PM and OPP support to the Host1x driver. For the starter we
> will keep host1x always-on because dynamic power management require a major
> refactoring of the driver code since lot's of code paths are missing the
> RPM handling and we're going to remove some of these paths in the future.
>
> Tested-by: Peter Geis <[email protected]> # Ouya T30
> Tested-by: Paul Fertser <[email protected]> # PAZ00 T20
> Tested-by: Nicolas Chauvet <[email protected]> # PAZ00 T20 and TK1 T124
> Tested-by: Matt Merhar <[email protected]> # Ouya T30
> Signed-off-by: Dmitry Osipenko <[email protected]>
> ---

[...]

> --- a/drivers/gpu/host1x/dev.c
> +++ b/drivers/gpu/host1x/dev.c
> @@ -6,14 +6,18 @@
> */
>
> #include <linux/clk.h>
> +#include <linux/delay.h>
> #include <linux/dma-mapping.h>
> #include <linux/io.h>
> #include <linux/list.h>
> #include <linux/module.h>
> #include <linux/of_device.h>
> #include <linux/of.h>
> +#include <linux/pm_runtime.h>
> #include <linux/slab.h>
>
> +#include <soc/tegra/common.h>
> +
> #define CREATE_TRACE_POINTS
> #include <trace/events/host1x.h>
> #undef CREATE_TRACE_POINTS
> @@ -190,6 +194,9 @@ static void host1x_setup_sid_table(struct host1x *host)
> const struct host1x_info *info = host->info;
> unsigned int i;
>
> + if (!info->has_hypervisor)
> + return;
> +
> for (i = 0; i < info->num_sid_entries; i++) {
> const struct host1x_sid_entry *entry = &info->sid_table[i];
>
> @@ -347,6 +354,27 @@ static void host1x_iommu_exit(struct host1x *host)
> }
> }
>
> +static int host1x_get_resets(struct host1x *host)
> +{
> + int err;
> +
> + host->resets[0].id = "mc";
> + host->resets[1].id = "host1x";
> + host->nresets = ARRAY_SIZE(host->resets);
> +
> + err = devm_reset_control_bulk_get_optional_exclusive_released(
> + host->dev, host->nresets, host->resets);
> + if (err) {
> + dev_err(host->dev, "failed to get reset: %d\n", err);
> + return err;
> + }
> +
> + if (WARN_ON(!host->resets[1].rstc))
> + return -ENOENT;
> +
> + return 0;
> +}
> +
> static int host1x_probe(struct platform_device *pdev)
> {
> struct host1x *host;
> @@ -423,12 +451,9 @@ static int host1x_probe(struct platform_device *pdev)
> return err;
> }
>
> - host->rst = devm_reset_control_get(&pdev->dev, "host1x");
> - if (IS_ERR(host->rst)) {
> - err = PTR_ERR(host->rst);
> - dev_err(&pdev->dev, "failed to get reset: %d\n", err);
> + err = host1x_get_resets(host);
> + if (err)
> return err;
> - }
>
> err = host1x_iommu_init(host);
> if (err < 0) {
> @@ -443,22 +468,10 @@ static int host1x_probe(struct platform_device *pdev)
> goto iommu_exit;
> }
>
> - err = clk_prepare_enable(host->clk);
> - if (err < 0) {
> - dev_err(&pdev->dev, "failed to enable clock\n");
> - goto free_channels;
> - }
> -
> - err = reset_control_deassert(host->rst);
> - if (err < 0) {
> - dev_err(&pdev->dev, "failed to deassert reset: %d\n", err);
> - goto unprepare_disable;
> - }
> -
> err = host1x_syncpt_init(host);
> if (err) {
> dev_err(&pdev->dev, "failed to initialize syncpts\n");
> - goto reset_assert;
> + goto free_channels;
> }
>
> err = host1x_intr_init(host, syncpt_irq);
> @@ -467,10 +480,18 @@ static int host1x_probe(struct platform_device *pdev)
> goto deinit_syncpt;
> }
>
> - host1x_debug_init(host);
> + pm_runtime_enable(&pdev->dev);
> +
> + err = devm_tegra_core_dev_init_opp_table_common(&pdev->dev);
> + if (err)
> + goto pm_disable;
>
> - if (host->info->has_hypervisor)
> - host1x_setup_sid_table(host);
> + /* the driver's code isn't ready yet for the dynamic RPM */
> + err = pm_runtime_resume_and_get(&pdev->dev);
> + if (err)
> + goto pm_disable;
> +
> + host1x_debug_init(host);
>
> err = host1x_register(host);
> if (err < 0)
> @@ -486,13 +507,14 @@ static int host1x_probe(struct platform_device *pdev)
> host1x_unregister(host);
> deinit_debugfs:
> host1x_debug_deinit(host);
> +
> + pm_runtime_put(&pdev->dev);

pm_runtime_put() is asynchronous, so it may not actually succeed to
trigger the ->runtime_suspend() callback to be invoked. Thus, this
could end up that we leave clocks prepared/enabled when ->probe()
fails, for example.

I guess pm_runtime_put_sync_suspend() is slightly better.

Another option is to call pm_runtime_force_suspend(), but then you
must skip the call pm_runtime_disable() afterwards, as that has
already been done inside that function.

> +pm_disable:
> + pm_runtime_disable(&pdev->dev);
> +
> host1x_intr_deinit(host);
> deinit_syncpt:
> host1x_syncpt_deinit(host);
> -reset_assert:
> - reset_control_assert(host->rst);
> -unprepare_disable:
> - clk_disable_unprepare(host->clk);
> free_channels:
> host1x_channel_list_free(&host->channel_list);
> iommu_exit:
> @@ -507,19 +529,94 @@ static int host1x_remove(struct platform_device *pdev)
>
> host1x_unregister(host);
> host1x_debug_deinit(host);
> +
> + pm_runtime_put(&pdev->dev);

Similar comment as in ->probe().

> + pm_runtime_disable(&pdev->dev);
> +
> host1x_intr_deinit(host);
> host1x_syncpt_deinit(host);
> - reset_control_assert(host->rst);
> - clk_disable_unprepare(host->clk);
> host1x_iommu_exit(host);
>
> return 0;
> }
>
> +
> + host1x_setup_sid_table(host);
> + host1x_syncpt_restore(host);
> + host1x_intr_start(host);
> +
> + return 0;
> +
> +disable_clk:
> + clk_disable_unprepare(host->clk);
> +release_reset:
> + reset_control_bulk_release(host->nresets, host->resets);
> +
> + return err;
> +}
> +
> +static const struct dev_pm_ops host1x_pm = {
> + SET_RUNTIME_PM_OPS(host1x_runtime_suspend, host1x_runtime_resume,
> + NULL)
> + /* TODO: add system suspend-resume once driver will be ready for that */
> +};

[...]

Kind regards
Uffe

2021-10-01 14:12:46

by Ulf Hansson

[permalink] [raw]
Subject: Re: [PATCH v13 14/35] drm/tegra: gr3d: Support generic power domain and runtime PM

On Mon, 27 Sept 2021 at 00:42, Dmitry Osipenko <[email protected]> wrote:
>
> Add runtime power management and support generic power domains.
>
> Tested-by: Peter Geis <[email protected]> # Ouya T30
> Tested-by: Paul Fertser <[email protected]> # PAZ00 T20
> Tested-by: Nicolas Chauvet <[email protected]> # PAZ00 T20 and TK1 T124
> Tested-by: Matt Merhar <[email protected]> # Ouya T30
> Signed-off-by: Dmitry Osipenko <[email protected]>
> ---
> drivers/gpu/drm/tegra/gr3d.c | 388 ++++++++++++++++++++++++++++++-----

[...]

> +
> +static int gr3d_probe(struct platform_device *pdev)
> +{
> + struct host1x_syncpt **syncpts;
> + struct gr3d *gr3d;
> + unsigned int i;
> + int err;
> +
> + gr3d = devm_kzalloc(&pdev->dev, sizeof(*gr3d), GFP_KERNEL);
> + if (!gr3d)
> + return -ENOMEM;
> +
> + platform_set_drvdata(pdev, gr3d);
> +
> + gr3d->soc = of_device_get_match_data(&pdev->dev);
> +
> + syncpts = devm_kzalloc(&pdev->dev, sizeof(*syncpts), GFP_KERNEL);
> + if (!syncpts)
> + return -ENOMEM;
> +
> + err = gr3d_get_clocks(&pdev->dev, gr3d);
> + if (err)
> + return err;
> +
> + err = gr3d_get_resets(&pdev->dev, gr3d);
> + if (err)
> + return err;
> +
> + err = gr3d_init_power(&pdev->dev, gr3d);
> + if (err)
> + return err;
> +
> INIT_LIST_HEAD(&gr3d->client.base.list);
> gr3d->client.base.ops = &gr3d_client_ops;
> gr3d->client.base.dev = &pdev->dev;
> @@ -352,20 +552,36 @@ static int gr3d_probe(struct platform_device *pdev)
> gr3d->client.version = gr3d->soc->version;
> gr3d->client.ops = &gr3d_ops;
>
> + pm_runtime_enable(&pdev->dev);
> + pm_runtime_use_autosuspend(&pdev->dev);
> + pm_runtime_set_autosuspend_delay(&pdev->dev, 200);
> +
> + err = devm_pm_opp_register_set_opp_helper(&pdev->dev, gr3d_set_opp);
> + if (err)
> + goto disable_rpm;
> +
> + err = devm_tegra_core_dev_init_opp_table_common(&pdev->dev);
> + if (err)
> + goto disable_rpm;
> +
> err = host1x_client_register(&gr3d->client.base);
> if (err < 0) {
> dev_err(&pdev->dev, "failed to register host1x client: %d\n",
> err);
> - return err;
> + goto disable_rpm;
> }
>
> /* initialize address register map */
> for (i = 0; i < ARRAY_SIZE(gr3d_addr_regs); i++)
> set_bit(gr3d_addr_regs[i], gr3d->addr_regs);
>
> - platform_set_drvdata(pdev, gr3d);
> -
> return 0;
> +
> +disable_rpm:
> + pm_runtime_dont_use_autosuspend(&pdev->dev);
> + pm_runtime_disable(&pdev->dev);

Similar comment as for patch13.

> +
> + return err;
> }
>
> static int gr3d_remove(struct platform_device *pdev)
> @@ -380,23 +596,83 @@ static int gr3d_remove(struct platform_device *pdev)
> return err;
> }
>
> - if (gr3d->clk_secondary) {
> - reset_control_assert(gr3d->rst_secondary);
> - tegra_powergate_power_off(TEGRA_POWERGATE_3D1);
> - clk_disable_unprepare(gr3d->clk_secondary);
> + pm_runtime_dont_use_autosuspend(&pdev->dev);
> + pm_runtime_disable(&pdev->dev);

Similar comment as for patch13. You may want to use
pm_runtime_force_suspend() in favor of pm_runtime_disable().

> +
> + return 0;
> +}

[...]

I was looking for a call to dev_pm_opp_set_rate(), but couldn't find
it. Isn't that needed when changing the rate of the clock?

Kind regards
Uffe

2021-10-01 14:27:26

by Ulf Hansson

[permalink] [raw]
Subject: Re: [PATCH v13 20/35] mtd: rawnand: tegra: Add runtime PM and OPP support

On Mon, 27 Sept 2021 at 00:42, Dmitry Osipenko <[email protected]> wrote:
>
> The NAND on Tegra belongs to the core power domain and we're going to
> enable GENPD support for the core domain. Now NAND must be resumed using
> runtime PM API in order to initialize the NAND power state. Add runtime PM
> and OPP support to the NAND driver.
>
> Acked-by: Miquel Raynal <[email protected]>
> Signed-off-by: Dmitry Osipenko <[email protected]>
> ---
> drivers/mtd/nand/raw/tegra_nand.c | 55 ++++++++++++++++++++++++++-----
> 1 file changed, 47 insertions(+), 8 deletions(-)
>
> diff --git a/drivers/mtd/nand/raw/tegra_nand.c b/drivers/mtd/nand/raw/tegra_nand.c
> index 32431bbe69b8..098fcc9cb9df 100644
> --- a/drivers/mtd/nand/raw/tegra_nand.c
> +++ b/drivers/mtd/nand/raw/tegra_nand.c
> @@ -17,8 +17,11 @@
> #include <linux/mtd/rawnand.h>
> #include <linux/of.h>
> #include <linux/platform_device.h>
> +#include <linux/pm_runtime.h>
> #include <linux/reset.h>
>
> +#include <soc/tegra/common.h>
> +
> #define COMMAND 0x00
> #define COMMAND_GO BIT(31)
> #define COMMAND_CLE BIT(30)
> @@ -1151,6 +1154,7 @@ static int tegra_nand_probe(struct platform_device *pdev)
> return -ENOMEM;
>
> ctrl->dev = &pdev->dev;
> + platform_set_drvdata(pdev, ctrl);
> nand_controller_init(&ctrl->controller);
> ctrl->controller.ops = &tegra_nand_controller_ops;
>
> @@ -1166,14 +1170,22 @@ static int tegra_nand_probe(struct platform_device *pdev)
> if (IS_ERR(ctrl->clk))
> return PTR_ERR(ctrl->clk);
>
> - err = clk_prepare_enable(ctrl->clk);
> + err = devm_pm_runtime_enable(&pdev->dev);
> + if (err)
> + return err;
> +
> + err = devm_tegra_core_dev_init_opp_table_common(&pdev->dev);
> + if (err)
> + return err;
> +
> + err = pm_runtime_resume_and_get(&pdev->dev);
> if (err)
> return err;
>
> err = reset_control_reset(rst);
> if (err) {
> dev_err(ctrl->dev, "Failed to reset HW: %d\n", err);
> - goto err_disable_clk;
> + goto err_put_pm;
> }
>
> writel_relaxed(HWSTATUS_CMD_DEFAULT, ctrl->regs + HWSTATUS_CMD);
> @@ -1188,21 +1200,19 @@ static int tegra_nand_probe(struct platform_device *pdev)
> dev_name(&pdev->dev), ctrl);
> if (err) {
> dev_err(ctrl->dev, "Failed to get IRQ: %d\n", err);
> - goto err_disable_clk;
> + goto err_put_pm;
> }
>
> writel_relaxed(DMA_MST_CTRL_IS_DONE, ctrl->regs + DMA_MST_CTRL);
>
> err = tegra_nand_chips_init(ctrl->dev, ctrl);
> if (err)
> - goto err_disable_clk;
> -
> - platform_set_drvdata(pdev, ctrl);
> + goto err_put_pm;
>

There is no corresponding call pm_runtime_put() here. Is it
intentional to always leave the device runtime resumed after ->probe()
has succeeded?

I noticed you included some comments about this for some other
drivers, as those needed more tweaks. Is that also the case for this
driver?

> return 0;
>
> -err_disable_clk:
> - clk_disable_unprepare(ctrl->clk);
> +err_put_pm:
> + pm_runtime_put(ctrl->dev);
> return err;
> }
>

[...]

Kind regards
Uffe

2021-10-01 14:31:27

by Dmitry Osipenko

[permalink] [raw]
Subject: Re: [PATCH v13 13/35] drm/tegra: gr2d: Support generic power domain and runtime PM

01.10.2021 16:39, Ulf Hansson пишет:
> On Mon, 27 Sept 2021 at 00:42, Dmitry Osipenko <[email protected]> wrote:
>>
>> Add runtime power management and support generic power domains.
>>
>> Tested-by: Peter Geis <[email protected]> # Ouya T30
>> Tested-by: Paul Fertser <[email protected]> # PAZ00 T20
>> Tested-by: Nicolas Chauvet <[email protected]> # PAZ00 T20 and TK1 T124
>> Tested-by: Matt Merhar <[email protected]> # Ouya T30
>> Signed-off-by: Dmitry Osipenko <[email protected]>
>> ---
>> drivers/gpu/drm/tegra/gr2d.c | 155 +++++++++++++++++++++++++++++++++--
>
> [...]
>
>> static int gr2d_remove(struct platform_device *pdev)
>> @@ -259,15 +312,101 @@ static int gr2d_remove(struct platform_device *pdev)
>> return err;
>> }
>>
>> + pm_runtime_dont_use_autosuspend(&pdev->dev);
>> + pm_runtime_disable(&pdev->dev);
>
> There is no guarantee that the ->runtime_suspend() has been invoked
> here, which means that clock may be left prepared/enabled beyond this
> point.
>
> I suggest you call pm_runtime_force_suspend(), instead of
> pm_runtime_disable(), to make sure that gets done.

The pm_runtime_disable() performs the final synchronization, please see [1].

[1]
https://elixir.bootlin.com/linux/v5.15-rc3/source/drivers/base/power/runtime.c#L1412

Calling pm_runtime_force_suspend() isn't correct because each 'enable'
must have the corresponding 'disable'. Hence there is no problem here.

2021-10-01 14:38:14

by Dmitry Osipenko

[permalink] [raw]
Subject: Re: [PATCH v13 20/35] mtd: rawnand: tegra: Add runtime PM and OPP support

01.10.2021 17:24, Ulf Hansson пишет:
> On Mon, 27 Sept 2021 at 00:42, Dmitry Osipenko <[email protected]> wrote:
>>
>> The NAND on Tegra belongs to the core power domain and we're going to
>> enable GENPD support for the core domain. Now NAND must be resumed using
>> runtime PM API in order to initialize the NAND power state. Add runtime PM
>> and OPP support to the NAND driver.
>>
>> Acked-by: Miquel Raynal <[email protected]>
>> Signed-off-by: Dmitry Osipenko <[email protected]>
>> ---
>> drivers/mtd/nand/raw/tegra_nand.c | 55 ++++++++++++++++++++++++++-----
>> 1 file changed, 47 insertions(+), 8 deletions(-)
>>
>> diff --git a/drivers/mtd/nand/raw/tegra_nand.c b/drivers/mtd/nand/raw/tegra_nand.c
>> index 32431bbe69b8..098fcc9cb9df 100644
>> --- a/drivers/mtd/nand/raw/tegra_nand.c
>> +++ b/drivers/mtd/nand/raw/tegra_nand.c
>> @@ -17,8 +17,11 @@
>> #include <linux/mtd/rawnand.h>
>> #include <linux/of.h>
>> #include <linux/platform_device.h>
>> +#include <linux/pm_runtime.h>
>> #include <linux/reset.h>
>>
>> +#include <soc/tegra/common.h>
>> +
>> #define COMMAND 0x00
>> #define COMMAND_GO BIT(31)
>> #define COMMAND_CLE BIT(30)
>> @@ -1151,6 +1154,7 @@ static int tegra_nand_probe(struct platform_device *pdev)
>> return -ENOMEM;
>>
>> ctrl->dev = &pdev->dev;
>> + platform_set_drvdata(pdev, ctrl);
>> nand_controller_init(&ctrl->controller);
>> ctrl->controller.ops = &tegra_nand_controller_ops;
>>
>> @@ -1166,14 +1170,22 @@ static int tegra_nand_probe(struct platform_device *pdev)
>> if (IS_ERR(ctrl->clk))
>> return PTR_ERR(ctrl->clk);
>>
>> - err = clk_prepare_enable(ctrl->clk);
>> + err = devm_pm_runtime_enable(&pdev->dev);
>> + if (err)
>> + return err;
>> +
>> + err = devm_tegra_core_dev_init_opp_table_common(&pdev->dev);
>> + if (err)
>> + return err;
>> +
>> + err = pm_runtime_resume_and_get(&pdev->dev);
>> if (err)
>> return err;
>>
>> err = reset_control_reset(rst);
>> if (err) {
>> dev_err(ctrl->dev, "Failed to reset HW: %d\n", err);
>> - goto err_disable_clk;
>> + goto err_put_pm;
>> }
>>
>> writel_relaxed(HWSTATUS_CMD_DEFAULT, ctrl->regs + HWSTATUS_CMD);
>> @@ -1188,21 +1200,19 @@ static int tegra_nand_probe(struct platform_device *pdev)
>> dev_name(&pdev->dev), ctrl);
>> if (err) {
>> dev_err(ctrl->dev, "Failed to get IRQ: %d\n", err);
>> - goto err_disable_clk;
>> + goto err_put_pm;
>> }
>>
>> writel_relaxed(DMA_MST_CTRL_IS_DONE, ctrl->regs + DMA_MST_CTRL);
>>
>> err = tegra_nand_chips_init(ctrl->dev, ctrl);
>> if (err)
>> - goto err_disable_clk;
>> -
>> - platform_set_drvdata(pdev, ctrl);
>> + goto err_put_pm;
>>
>
> There is no corresponding call pm_runtime_put() here. Is it
> intentional to always leave the device runtime resumed after ->probe()
> has succeeded?
>
> I noticed you included some comments about this for some other
> drivers, as those needed more tweaks. Is that also the case for this
> driver?

Could you please clarify? There is pm_runtime_put() in both probe-error
and remove() code paths here.

I assume you're meaning pm_runtime_disable(), but this patch uses
resource-managed devm_pm_runtime_enable(), and thus, explicit disable
isn't needed.

>> return 0;
>>
>> -err_disable_clk:
>> - clk_disable_unprepare(ctrl->clk);
>> +err_put_pm:
>> + pm_runtime_put(ctrl->dev);
>> return err;
>> }
>>
>
> [...]
>
> Kind regards
> Uffe
>

2021-10-01 14:39:32

by Ulf Hansson

[permalink] [raw]
Subject: Re: [PATCH v13 00/35] NVIDIA Tegra power management patches for 5.16

On Mon, 27 Sept 2021 at 00:42, Dmitry Osipenko <[email protected]> wrote:
>
> This series adds runtime PM support to Tegra drivers and enables core
> voltage scaling for Tegra20/30 SoCs, resolving overheating troubles.
>
> All patches in this series are interdependent and should go via Tegra tree.
>
> Changelog:
>
> v13: - Fixed compile-test error reported by build bot by reverting the
> mmc/ patch to v11. The sdhci_suspend/resume_host() functions aren't
> available with the disabled CONFIG_PM_SLEEP, some code needs the
> ifdef.
>
> - Added last r-b from Rob Herring for the DT patches.
>
> - Corrected clk/ PM domain-support patch by not using the
> devm_tegra_core_dev_init_opp_table_common() helper, which I
> utilized in v12. The clk driver implements its own power domain
> state syncing and common helper shouldn't be used. This fixes driver
> probing for some clocks on some devices. It was reported by
> Svyatoslav Ryhel for PLLE OPP error on T30 Asus Transformer tablet.

Dmitry, I have looked through the series and besides those comments
that I have posted, I have nothing more to add. Overall it looks good
to me.

Kind regards
Uffe

2021-10-01 14:43:35

by Dmitry Osipenko

[permalink] [raw]
Subject: Re: [PATCH v13 00/35] NVIDIA Tegra power management patches for 5.16

01.10.2021 17:36, Ulf Hansson пишет:
> On Mon, 27 Sept 2021 at 00:42, Dmitry Osipenko <[email protected]> wrote:
>>
>> This series adds runtime PM support to Tegra drivers and enables core
>> voltage scaling for Tegra20/30 SoCs, resolving overheating troubles.
>>
>> All patches in this series are interdependent and should go via Tegra tree.
>>
>> Changelog:
>>
>> v13: - Fixed compile-test error reported by build bot by reverting the
>> mmc/ patch to v11. The sdhci_suspend/resume_host() functions aren't
>> available with the disabled CONFIG_PM_SLEEP, some code needs the
>> ifdef.
>>
>> - Added last r-b from Rob Herring for the DT patches.
>>
>> - Corrected clk/ PM domain-support patch by not using the
>> devm_tegra_core_dev_init_opp_table_common() helper, which I
>> utilized in v12. The clk driver implements its own power domain
>> state syncing and common helper shouldn't be used. This fixes driver
>> probing for some clocks on some devices. It was reported by
>> Svyatoslav Ryhel for PLLE OPP error on T30 Asus Transformer tablet.
>
> Dmitry, I have looked through the series and besides those comments
> that I have posted, I have nothing more to add. Overall it looks good
> to me.

Ulf, thank you very much! Yours input is invaluable. I'm happy that this
series moving steadily to the final stage.

2021-10-01 14:56:59

by Ulf Hansson

[permalink] [raw]
Subject: Re: [PATCH v13 13/35] drm/tegra: gr2d: Support generic power domain and runtime PM

On Fri, 1 Oct 2021 at 16:29, Dmitry Osipenko <[email protected]> wrote:
>
> 01.10.2021 16:39, Ulf Hansson пишет:
> > On Mon, 27 Sept 2021 at 00:42, Dmitry Osipenko <[email protected]> wrote:
> >>
> >> Add runtime power management and support generic power domains.
> >>
> >> Tested-by: Peter Geis <[email protected]> # Ouya T30
> >> Tested-by: Paul Fertser <[email protected]> # PAZ00 T20
> >> Tested-by: Nicolas Chauvet <[email protected]> # PAZ00 T20 and TK1 T124
> >> Tested-by: Matt Merhar <[email protected]> # Ouya T30
> >> Signed-off-by: Dmitry Osipenko <[email protected]>
> >> ---
> >> drivers/gpu/drm/tegra/gr2d.c | 155 +++++++++++++++++++++++++++++++++--
> >
> > [...]
> >
> >> static int gr2d_remove(struct platform_device *pdev)
> >> @@ -259,15 +312,101 @@ static int gr2d_remove(struct platform_device *pdev)
> >> return err;
> >> }
> >>
> >> + pm_runtime_dont_use_autosuspend(&pdev->dev);
> >> + pm_runtime_disable(&pdev->dev);
> >
> > There is no guarantee that the ->runtime_suspend() has been invoked
> > here, which means that clock may be left prepared/enabled beyond this
> > point.
> >
> > I suggest you call pm_runtime_force_suspend(), instead of
> > pm_runtime_disable(), to make sure that gets done.
>
> The pm_runtime_disable() performs the final synchronization, please see [1].
>
> [1]
> https://elixir.bootlin.com/linux/v5.15-rc3/source/drivers/base/power/runtime.c#L1412

pm_runtime_disable() end up calling _pm_runtime_barrier(), which calls
cancel_work_sync() if dev->power.request_pending has been set.

If the work that was punted to the pm_wq in rpm_idle() has not been
started yet, we end up just canceling it. In other words, there are no
guarantees it runs to completion.

Moreover, use space may have bumped the usage count via sysfs for the
device (pm_runtime_forbid()) to keep the device runtime resumed.

>
> Calling pm_runtime_force_suspend() isn't correct because each 'enable'
> must have the corresponding 'disable'. Hence there is no problem here.

pm_runtime_force_suspend() calls pm_runtime_disable(), so I think that
should be fine. No?

Kind regards
Uffe

2021-10-01 15:07:02

by Ulf Hansson

[permalink] [raw]
Subject: Re: [PATCH v13 00/35] NVIDIA Tegra power management patches for 5.16

On Fri, 1 Oct 2021 at 16:41, Dmitry Osipenko <[email protected]> wrote:
>
> 01.10.2021 17:36, Ulf Hansson пишет:
> > On Mon, 27 Sept 2021 at 00:42, Dmitry Osipenko <[email protected]> wrote:
> >>
> >> This series adds runtime PM support to Tegra drivers and enables core
> >> voltage scaling for Tegra20/30 SoCs, resolving overheating troubles.
> >>
> >> All patches in this series are interdependent and should go via Tegra tree.
> >>
> >> Changelog:
> >>
> >> v13: - Fixed compile-test error reported by build bot by reverting the
> >> mmc/ patch to v11. The sdhci_suspend/resume_host() functions aren't
> >> available with the disabled CONFIG_PM_SLEEP, some code needs the
> >> ifdef.
> >>
> >> - Added last r-b from Rob Herring for the DT patches.
> >>
> >> - Corrected clk/ PM domain-support patch by not using the
> >> devm_tegra_core_dev_init_opp_table_common() helper, which I
> >> utilized in v12. The clk driver implements its own power domain
> >> state syncing and common helper shouldn't be used. This fixes driver
> >> probing for some clocks on some devices. It was reported by
> >> Svyatoslav Ryhel for PLLE OPP error on T30 Asus Transformer tablet.
> >
> > Dmitry, I have looked through the series and besides those comments
> > that I have posted, I have nothing more to add. Overall it looks good
> > to me.
>
> Ulf, thank you very much! Yours input is invaluable. I'm happy that this
> series moving steadily to the final stage.

My pleasure. Let's get the final pieces fixed so we can get this merged! :-)

Kind regards
Uffe

2021-10-01 15:49:45

by Ulf Hansson

[permalink] [raw]
Subject: Re: [PATCH v13 13/35] drm/tegra: gr2d: Support generic power domain and runtime PM

On Mon, 27 Sept 2021 at 00:42, Dmitry Osipenko <[email protected]> wrote:
>
> Add runtime power management and support generic power domains.
>
> Tested-by: Peter Geis <[email protected]> # Ouya T30
> Tested-by: Paul Fertser <[email protected]> # PAZ00 T20
> Tested-by: Nicolas Chauvet <[email protected]> # PAZ00 T20 and TK1 T124
> Tested-by: Matt Merhar <[email protected]> # Ouya T30
> Signed-off-by: Dmitry Osipenko <[email protected]>
> ---
> drivers/gpu/drm/tegra/gr2d.c | 155 +++++++++++++++++++++++++++++++++--

[...]

> static int gr2d_remove(struct platform_device *pdev)
> @@ -259,15 +312,101 @@ static int gr2d_remove(struct platform_device *pdev)
> return err;
> }
>
> + pm_runtime_dont_use_autosuspend(&pdev->dev);
> + pm_runtime_disable(&pdev->dev);

There is no guarantee that the ->runtime_suspend() has been invoked
here, which means that clock may be left prepared/enabled beyond this
point.

I suggest you call pm_runtime_force_suspend(), instead of
pm_runtime_disable(), to make sure that gets done.

[...]

Kind regards
Uffe

2021-10-01 15:56:53

by Ulf Hansson

[permalink] [raw]
Subject: Re: [PATCH v13 17/35] bus: tegra-gmi: Add runtime PM and OPP support

On Mon, 27 Sept 2021 at 00:42, Dmitry Osipenko <[email protected]> wrote:
>
> The GMI bus on Tegra belongs to the core power domain and we're going to
> enable GENPD support for the core domain. Now GMI must be resumed using
> runtime PM API in order to initialize the GMI power state. Add runtime PM
> and OPP support to the GMI driver.
>
> Signed-off-by: Dmitry Osipenko <[email protected]>
> ---
> drivers/bus/tegra-gmi.c | 52 ++++++++++++++++++++++++++++++++++++-----
> 1 file changed, 46 insertions(+), 6 deletions(-)
>
> diff --git a/drivers/bus/tegra-gmi.c b/drivers/bus/tegra-gmi.c
> index a6570789f7af..72ef8a8c236b 100644
> --- a/drivers/bus/tegra-gmi.c
> +++ b/drivers/bus/tegra-gmi.c
> @@ -13,8 +13,11 @@
> #include <linux/io.h>
> #include <linux/module.h>
> #include <linux/of_device.h>
> +#include <linux/pm_runtime.h>
> #include <linux/reset.h>
>
> +#include <soc/tegra/common.h>
> +
> #define TEGRA_GMI_CONFIG 0x00
> #define TEGRA_GMI_CONFIG_GO BIT(31)
> #define TEGRA_GMI_BUS_WIDTH_32BIT BIT(30)
> @@ -54,9 +57,9 @@ static int tegra_gmi_enable(struct tegra_gmi *gmi)
> {
> int err;
>
> - err = clk_prepare_enable(gmi->clk);
> - if (err < 0) {
> - dev_err(gmi->dev, "failed to enable clock: %d\n", err);
> + err = pm_runtime_resume_and_get(gmi->dev);
> + if (err) {
> + pm_runtime_disable(gmi->dev);
> return err;
> }
>
> @@ -83,7 +86,8 @@ static void tegra_gmi_disable(struct tegra_gmi *gmi)
> writel(config, gmi->base + TEGRA_GMI_CONFIG);
>
> reset_control_assert(gmi->rst);
> - clk_disable_unprepare(gmi->clk);
> +
> + pm_runtime_put(gmi->dev);
> }
>
> static int tegra_gmi_parse_dt(struct tegra_gmi *gmi)
> @@ -213,6 +217,7 @@ static int tegra_gmi_probe(struct platform_device *pdev)
> if (!gmi)
> return -ENOMEM;
>
> + platform_set_drvdata(pdev, gmi);
> gmi->dev = dev;
>
> res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> @@ -232,6 +237,14 @@ static int tegra_gmi_probe(struct platform_device *pdev)
> return PTR_ERR(gmi->rst);
> }
>
> + err = devm_pm_runtime_enable(gmi->dev);
> + if (err)
> + return err;
> +
> + err = devm_tegra_core_dev_init_opp_table_common(&pdev->dev);
> + if (err)
> + return err;
> +
> err = tegra_gmi_parse_dt(gmi);
> if (err)
> return err;
> @@ -247,8 +260,6 @@ static int tegra_gmi_probe(struct platform_device *pdev)
> return err;
> }
>
> - platform_set_drvdata(pdev, gmi);
> -
> return 0;
> }
>
> @@ -262,6 +273,34 @@ static int tegra_gmi_remove(struct platform_device *pdev)

Similar comment as for patch13, for the ->remove() callback.

This problem, which sometimes also exists in the error path in
->probe() (according to my comments in patch13), seems to be a common
issue in the series. I will therefore not continue to repeat my
comment on this for the remaining patches in the series, I think I
have made my point. :-)

Kind regards
Uffe

2021-10-01 17:00:44

by Ulf Hansson

[permalink] [raw]
Subject: Re: [PATCH v13 06/35] clk: tegra: Support runtime PM and power domain

On Mon, 27 Sept 2021 at 00:42, Dmitry Osipenko <[email protected]> wrote:
>
> The Clock-and-Reset controller resides in a core power domain on NVIDIA
> Tegra SoCs. In order to support voltage scaling of the core power domain,
> we hook up DVFS-capable clocks to the core GENPD for managing of the
> GENPD's performance state based on the clock changes.
>
> Some clocks don't have any specific physical hardware unit that backs
> them, like root PLLs and system clock and they have theirs own voltage
> requirements. This patch adds new clk-device driver that backs the clocks
> and provides runtime PM functionality for them. A virtual clk-device is
> created for each such DVFS-capable clock at the clock's registration time
> by the new tegra_clk_register() helper. Driver changes clock's device
> GENPD performance state based on clk-rate notifications.
>
> In result we have this sequence of events:
>
> 1. Clock driver creates virtual device for selective clocks, enables
> runtime PM for the created device and registers the clock.
> 2. Clk-device driver starts to listen to clock rate changes.
> 3. Something changes clk rate or enables/disables clk.
> 4. CCF core propagates the change through the clk tree.
> 5. Clk-device driver gets clock rate-change notification or GENPD core
> handles prepare/unprepare of the clock.
> 6. Clk-device driver changes GENPD performance state on clock rate
> change.
> 7. GENPD driver changes voltage regulator state change.
> 8. The regulator state is committed to hardware via I2C.
>
> We rely on fact that DVFS is not needed for Tegra I2C and that Tegra I2C
> driver already keeps clock always-prepared. Hence I2C subsystem stays
> independent from the clk power management and there are no deadlock spots
> in the sequence.
>
> Currently all clocks are registered very early during kernel boot when the
> device driver core isn't available yet. The clk-device can't be created
> at that time. This patch splits the registration of the clocks in two
> phases:
>
> 1. Register all essential clocks which don't use RPM and are needed
> during early boot.
>
> 2. Register at a later boot time the rest of clocks.
>
> This patch adds power management support for Tegra20 and Tegra30 clocks.
>
> Tested-by: Peter Geis <[email protected]> # Ouya T30
> Tested-by: Paul Fertser <[email protected]> # PAZ00 T20
> Tested-by: Nicolas Chauvet <[email protected]> # PAZ00 T20 and TK1 T124
> Tested-by: Matt Merhar <[email protected]> # Ouya T30
> Signed-off-by: Dmitry Osipenko <[email protected]>
> ---
> drivers/clk/tegra/Makefile | 1 +
> drivers/clk/tegra/clk-device.c | 230 ++++++++++++++++++++++++++++++++
> drivers/clk/tegra/clk-pll.c | 2 +-
> drivers/clk/tegra/clk-super.c | 2 +-
> drivers/clk/tegra/clk-tegra20.c | 77 ++++++++---
> drivers/clk/tegra/clk-tegra30.c | 116 +++++++++++-----
> drivers/clk/tegra/clk.c | 75 ++++++++++-
> drivers/clk/tegra/clk.h | 2 +
> 8 files changed, 451 insertions(+), 54 deletions(-)
> create mode 100644 drivers/clk/tegra/clk-device.c
>
> diff --git a/drivers/clk/tegra/Makefile b/drivers/clk/tegra/Makefile
> index 7b1816856eb5..a0715cdfc1a4 100644
> --- a/drivers/clk/tegra/Makefile
> +++ b/drivers/clk/tegra/Makefile
> @@ -1,6 +1,7 @@
> # SPDX-License-Identifier: GPL-2.0
> obj-y += clk.o
> obj-y += clk-audio-sync.o
> +obj-y += clk-device.o
> obj-y += clk-dfll.o
> obj-y += clk-divider.o
> obj-y += clk-periph.o
> diff --git a/drivers/clk/tegra/clk-device.c b/drivers/clk/tegra/clk-device.c
> new file mode 100644
> index 000000000000..830bc0ba25d3
> --- /dev/null
> +++ b/drivers/clk/tegra/clk-device.c
> @@ -0,0 +1,230 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +
> +#include <linux/clk.h>
> +#include <linux/clk-provider.h>
> +#include <linux/mutex.h>
> +#include <linux/of_device.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_domain.h>
> +#include <linux/pm_opp.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/slab.h>
> +
> +#include <soc/tegra/common.h>
> +
> +#include "clk.h"
> +
> +/*
> + * This driver manages performance state of the core power domain for the
> + * independent PLLs and system clocks. We created a virtual clock device
> + * for such clocks, see tegra_clk_dev_register().
> + */
> +
> +struct tegra_clk_device {
> + struct notifier_block clk_nb;
> + struct device *dev;
> + struct clk_hw *hw;
> + struct mutex lock;
> +};
> +
> +static int tegra_clock_set_pd_state(struct tegra_clk_device *clk_dev,
> + unsigned long rate)
> +{
> + struct device *dev = clk_dev->dev;
> + struct dev_pm_opp *opp;
> + unsigned int pstate;
> +
> + opp = dev_pm_opp_find_freq_ceil(dev, &rate);
> + if (opp == ERR_PTR(-ERANGE)) {
> + dev_dbg(dev, "failed to find ceil OPP for %luHz\n", rate);
> + opp = dev_pm_opp_find_freq_floor(dev, &rate);
> + }
> +
> + if (IS_ERR(opp)) {
> + dev_err(dev, "failed to find OPP for %luHz: %pe\n", rate, opp);
> + return PTR_ERR(opp);
> + }
> +
> + pstate = dev_pm_opp_get_required_pstate(opp, 0);
> + dev_pm_opp_put(opp);
> +
> + return dev_pm_genpd_set_performance_state(dev, pstate);

The above code certainly looks like it can be made generic through a
common opp helper. I know we have discussed this before, so I am not
saying you should change right now.

Let's instead see what I think (and Viresh), when I have reviewed the
entire series.

> +}
> +
> +static int tegra_clock_change_notify(struct notifier_block *nb,
> + unsigned long msg, void *data)
> +{
> + struct clk_notifier_data *cnd = data;
> + struct tegra_clk_device *clk_dev;
> + int err = 0;
> +
> + clk_dev = container_of(nb, struct tegra_clk_device, clk_nb);
> +
> + mutex_lock(&clk_dev->lock);
> + switch (msg) {
> + case PRE_RATE_CHANGE:
> + if (cnd->new_rate > cnd->old_rate)
> + err = tegra_clock_set_pd_state(clk_dev, cnd->new_rate);
> + break;
> +
> + case ABORT_RATE_CHANGE:
> + err = tegra_clock_set_pd_state(clk_dev, cnd->old_rate);
> + break;
> +
> + case POST_RATE_CHANGE:
> + if (cnd->new_rate < cnd->old_rate)
> + err = tegra_clock_set_pd_state(clk_dev, cnd->new_rate);
> + break;
> +
> + default:
> + break;
> + }
> + mutex_unlock(&clk_dev->lock);
> +
> + return notifier_from_errno(err);
> +}
> +
> +static int tegra_clock_sync_pd_state(struct tegra_clk_device *clk_dev)
> +{
> + unsigned long rate;
> + int ret = 0;
> +
> + mutex_lock(&clk_dev->lock);
> +
> + if (!pm_runtime_status_suspended(clk_dev->dev)) {
> + rate = clk_hw_get_rate(clk_dev->hw);
> + ret = tegra_clock_set_pd_state(clk_dev, rate);

Don't we need to sync the performance state even when the device is
runtime suspended?

Perhaps the clock, via a child-clock for example, can get
prepared/enabled (hence its device gets runtime resumed) before there
is a clock rate update for it. Then there is no performance state set
for it, right? Or maybe that isn't a problem?

> + }
> +
> + mutex_unlock(&clk_dev->lock);
> +
> + return ret;
> +}
> +
> +static int tegra_clock_probe(struct platform_device *pdev)
> +{
> + struct tegra_core_opp_params opp_params = {};
> + struct tegra_clk_device *clk_dev;
> + struct device *dev = &pdev->dev;
> + struct clk *clk;
> + int err;
> +
> + if (!dev->pm_domain)
> + return -EINVAL;
> +
> + clk_dev = devm_kzalloc(dev, sizeof(*clk_dev), GFP_KERNEL);
> + if (!clk_dev)
> + return -ENOMEM;
> +
> + clk = devm_clk_get(dev, NULL);
> + if (IS_ERR(clk))
> + return PTR_ERR(clk);
> +
> + clk_dev->dev = dev;
> + clk_dev->hw = __clk_get_hw(clk);
> + clk_dev->clk_nb.notifier_call = tegra_clock_change_notify;
> + mutex_init(&clk_dev->lock);
> +
> + platform_set_drvdata(pdev, clk_dev);
> +
> + /*
> + * Runtime PM was already enabled for this device by the parent clk
> + * driver and power domain state should be synced under clk_dev lock,
> + * hence we don't use the common OPP helper that initializes OPP
> + * state. For some clocks common OPP helper may fail to find ceil
> + * rate, it's handled by this driver.
> + */
> + err = devm_tegra_core_dev_init_opp_table(dev, &opp_params);
> + if (err)
> + return err;
> +
> + err = clk_notifier_register(clk, &clk_dev->clk_nb);
> + if (err) {
> + dev_err(dev, "failed to register clk notifier: %d\n", err);
> + return err;
> + }
> +
> + /*
> + * The driver is attaching to a potentially active/resumed clock, hence
> + * we need to sync the power domain performance state in a accordance to
> + * the clock rate if clock is resumed.
> + */
> + err = tegra_clock_sync_pd_state(clk_dev);
> + if (err)
> + goto unreg_clk;
> +
> + return 0;
> +
> +unreg_clk:
> + clk_notifier_unregister(clk, &clk_dev->clk_nb);
> +
> + return err;
> +}
> +
> +static __maybe_unused int tegra_clock_pm_suspend(struct device *dev)
> +{
> + struct tegra_clk_device *clk_dev = dev_get_drvdata(dev);
> +
> + /*
> + * Power management of the clock is entangled with the Tegra PMC
> + * GENPD because PMC driver enables/disables clocks for toggling
> + * of the PD's on/off state.
> + *
> + * The PMC GENPD is resumed in NOIRQ phase, before RPM of the clocks
> + * becomes available, hence PMC can't use clocks at the early resume
> + * phase if RPM is involved. For example when 3d clock is enabled,
> + * it may enable the parent PLL clock that needs to be RPM-resumed.
> + *
> + * Secondly, the PLL clocks may be enabled by the low level suspend
> + * code, so we need to assume that PLL is in enabled state during
> + * suspend.
> + *
> + * We will keep PLLs and system clock resumed during suspend time.
> + * All PLLs on all SoCs are low power and system clock is always-on,
> + * so practically not much is changed here.
> + */
> +
> + return clk_prepare(clk_dev->hw->clk);

I am trying to understand, more exactly, what you intend to achieve
with the clk_prepare() here. It looks a bit weird, to me. Can you try
to elaborate a bit more on the use case?

Is this rather about making sure that the clock's corresponding PM
domain stays powered on during system suspend? In that case, I think
there may be an alternative option....

> +}
> +
> +static __maybe_unused int tegra_clock_pm_resume(struct device *dev)
> +{
> + struct tegra_clk_device *clk_dev = dev_get_drvdata(dev);
> +
> + clk_unprepare(clk_dev->hw->clk);
> +
> + return 0;
> +}
> +
> +static void tegra_clock_shutdown(struct platform_device *pdev)
> +{
> + struct tegra_clk_device *clk_dev = platform_get_drvdata(pdev);
> +
> + clk_prepare(clk_dev->hw->clk);
> +}
> +
> +static const struct dev_pm_ops tegra_clock_pm = {
> + SET_SYSTEM_SLEEP_PM_OPS(tegra_clock_pm_suspend,
> + tegra_clock_pm_resume)
> +};

[...]

Kind regards
Uffe

2021-10-01 17:02:14

by Ulf Hansson

[permalink] [raw]
Subject: Re: [PATCH v13 11/35] drm/tegra: dc: Support OPP and SoC core voltage scaling

On Mon, 27 Sept 2021 at 00:42, Dmitry Osipenko <[email protected]> wrote:
>
> Add OPP and SoC core voltage scaling support to the display controller
> driver. This is required for enabling system-wide DVFS on pre-Tegra186
> SoCs.
>
> Tested-by: Peter Geis <[email protected]> # Ouya T30
> Tested-by: Paul Fertser <[email protected]> # PAZ00 T20
> Tested-by: Nicolas Chauvet <[email protected]> # PAZ00 T20 and TK1 T124
> Tested-by: Matt Merhar <[email protected]> # Ouya T30
> Signed-off-by: Dmitry Osipenko <[email protected]>
> ---
> drivers/gpu/drm/tegra/dc.c | 74 ++++++++++++++++++++++++++++++++++++++
> drivers/gpu/drm/tegra/dc.h | 2 ++
> 2 files changed, 76 insertions(+)
>
> diff --git a/drivers/gpu/drm/tegra/dc.c b/drivers/gpu/drm/tegra/dc.c
> index a29d64f87563..d4047a14e2b6 100644
> --- a/drivers/gpu/drm/tegra/dc.c
> +++ b/drivers/gpu/drm/tegra/dc.c
> @@ -11,9 +11,12 @@
> #include <linux/interconnect.h>
> #include <linux/module.h>
> #include <linux/of_device.h>
> +#include <linux/pm_domain.h>
> +#include <linux/pm_opp.h>
> #include <linux/pm_runtime.h>
> #include <linux/reset.h>
>
> +#include <soc/tegra/common.h>
> #include <soc/tegra/pmc.h>
>
> #include <drm/drm_atomic.h>
> @@ -1762,6 +1765,47 @@ int tegra_dc_state_setup_clock(struct tegra_dc *dc,
> return 0;
> }
>
> +static void tegra_dc_update_voltage_state(struct tegra_dc *dc,
> + struct tegra_dc_state *state)
> +{
> + unsigned long rate, pstate;
> + struct dev_pm_opp *opp;
> + int err;
> +
> + if (!dc->has_opp_table)
> + return;
> +
> + /* calculate actual pixel clock rate which depends on internal divider */
> + rate = DIV_ROUND_UP(clk_get_rate(dc->clk) * 2, state->div + 2);
> +
> + /* find suitable OPP for the rate */
> + opp = dev_pm_opp_find_freq_ceil(dc->dev, &rate);
> +
> + if (opp == ERR_PTR(-ERANGE))
> + opp = dev_pm_opp_find_freq_floor(dc->dev, &rate);
> +
> + if (IS_ERR(opp)) {
> + dev_err(dc->dev, "failed to find OPP for %luHz: %pe\n",
> + rate, opp);
> + return;
> + }
> +
> + pstate = dev_pm_opp_get_required_pstate(opp, 0);
> + dev_pm_opp_put(opp);
> +
> + /*
> + * The minimum core voltage depends on the pixel clock rate (which
> + * depends on internal clock divider of the CRTC) and not on the
> + * rate of the display controller clock. This is why we're not using
> + * dev_pm_opp_set_rate() API and instead controlling the power domain
> + * directly.
> + */
> + err = dev_pm_genpd_set_performance_state(dc->dev, pstate);
> + if (err)
> + dev_err(dc->dev, "failed to set power domain state to %lu: %d\n",
> + pstate, err);

Yeah, the above code looks very similar to the code I pointed to in
patch6. Perhaps we need to discuss with Viresh, whether it makes sense
to fold in a patch adding an opp helper function after all, to avoid
the open coding.

Viresh?

[...]

Kind regards
Uffe

2021-10-01 17:17:59

by Ulf Hansson

[permalink] [raw]
Subject: Re: [PATCH v13 20/35] mtd: rawnand: tegra: Add runtime PM and OPP support

On Fri, 1 Oct 2021 at 16:35, Dmitry Osipenko <[email protected]> wrote:
>
> 01.10.2021 17:24, Ulf Hansson пишет:
> > On Mon, 27 Sept 2021 at 00:42, Dmitry Osipenko <[email protected]> wrote:
> >>
> >> The NAND on Tegra belongs to the core power domain and we're going to
> >> enable GENPD support for the core domain. Now NAND must be resumed using
> >> runtime PM API in order to initialize the NAND power state. Add runtime PM
> >> and OPP support to the NAND driver.
> >>
> >> Acked-by: Miquel Raynal <[email protected]>
> >> Signed-off-by: Dmitry Osipenko <[email protected]>
> >> ---
> >> drivers/mtd/nand/raw/tegra_nand.c | 55 ++++++++++++++++++++++++++-----
> >> 1 file changed, 47 insertions(+), 8 deletions(-)
> >>
> >> diff --git a/drivers/mtd/nand/raw/tegra_nand.c b/drivers/mtd/nand/raw/tegra_nand.c
> >> index 32431bbe69b8..098fcc9cb9df 100644
> >> --- a/drivers/mtd/nand/raw/tegra_nand.c
> >> +++ b/drivers/mtd/nand/raw/tegra_nand.c
> >> @@ -17,8 +17,11 @@
> >> #include <linux/mtd/rawnand.h>
> >> #include <linux/of.h>
> >> #include <linux/platform_device.h>
> >> +#include <linux/pm_runtime.h>
> >> #include <linux/reset.h>
> >>
> >> +#include <soc/tegra/common.h>
> >> +
> >> #define COMMAND 0x00
> >> #define COMMAND_GO BIT(31)
> >> #define COMMAND_CLE BIT(30)
> >> @@ -1151,6 +1154,7 @@ static int tegra_nand_probe(struct platform_device *pdev)
> >> return -ENOMEM;
> >>
> >> ctrl->dev = &pdev->dev;
> >> + platform_set_drvdata(pdev, ctrl);
> >> nand_controller_init(&ctrl->controller);
> >> ctrl->controller.ops = &tegra_nand_controller_ops;
> >>
> >> @@ -1166,14 +1170,22 @@ static int tegra_nand_probe(struct platform_device *pdev)
> >> if (IS_ERR(ctrl->clk))
> >> return PTR_ERR(ctrl->clk);
> >>
> >> - err = clk_prepare_enable(ctrl->clk);
> >> + err = devm_pm_runtime_enable(&pdev->dev);
> >> + if (err)
> >> + return err;
> >> +
> >> + err = devm_tegra_core_dev_init_opp_table_common(&pdev->dev);
> >> + if (err)
> >> + return err;
> >> +
> >> + err = pm_runtime_resume_and_get(&pdev->dev);
> >> if (err)
> >> return err;
> >>
> >> err = reset_control_reset(rst);
> >> if (err) {
> >> dev_err(ctrl->dev, "Failed to reset HW: %d\n", err);
> >> - goto err_disable_clk;
> >> + goto err_put_pm;
> >> }
> >>
> >> writel_relaxed(HWSTATUS_CMD_DEFAULT, ctrl->regs + HWSTATUS_CMD);
> >> @@ -1188,21 +1200,19 @@ static int tegra_nand_probe(struct platform_device *pdev)
> >> dev_name(&pdev->dev), ctrl);
> >> if (err) {
> >> dev_err(ctrl->dev, "Failed to get IRQ: %d\n", err);
> >> - goto err_disable_clk;
> >> + goto err_put_pm;
> >> }
> >>
> >> writel_relaxed(DMA_MST_CTRL_IS_DONE, ctrl->regs + DMA_MST_CTRL);
> >>
> >> err = tegra_nand_chips_init(ctrl->dev, ctrl);
> >> if (err)
> >> - goto err_disable_clk;
> >> -
> >> - platform_set_drvdata(pdev, ctrl);
> >> + goto err_put_pm;
> >>
> >
> > There is no corresponding call pm_runtime_put() here. Is it
> > intentional to always leave the device runtime resumed after ->probe()
> > has succeeded?
> >
> > I noticed you included some comments about this for some other
> > drivers, as those needed more tweaks. Is that also the case for this
> > driver?
>
> Could you please clarify? There is pm_runtime_put() in both probe-error
> and remove() code paths here.

I was not considering the error path of ->probe() (or ->remove()), but
was rather thinking about when ->probe() completes successfully. Then
you keep the device runtime resumed, because you have called
pm_runtime_resume_and_get() for it.

Shouldn't you have a corresponding pm_runtime_put() in ->probe(),
allowing it to be runtime suspended, until the device is really needed
later on. No?

>
> I assume you're meaning pm_runtime_disable(), but this patch uses
> resource-managed devm_pm_runtime_enable(), and thus, explicit disable
> isn't needed.
>
> >> return 0;
> >>
> >> -err_disable_clk:
> >> - clk_disable_unprepare(ctrl->clk);
> >> +err_put_pm:
> >> + pm_runtime_put(ctrl->dev);
> >> return err;
> >> }
> >>

[...]

Kind regards
Uffe

2021-10-01 19:29:04

by Dmitry Osipenko

[permalink] [raw]
Subject: Re: [PATCH v13 02/35] soc/tegra: Add devm_tegra_core_dev_init_opp_table_common()

01.10.2021 15:50, Ulf Hansson пишет:
> On Mon, 27 Sept 2021 at 00:42, Dmitry Osipenko <[email protected]> wrote:
>>
>> Only couple drivers need to get the -ENODEV error code and majority of
>> drivers need to explicitly initialize the performance state. Add new
>> common helper which sets up OPP table for these drivers.
>>
>> Signed-off-by: Dmitry Osipenko <[email protected]>
>> ---
>> include/soc/tegra/common.h | 24 ++++++++++++++++++++++++
>> 1 file changed, 24 insertions(+)
>>
>> diff --git a/include/soc/tegra/common.h b/include/soc/tegra/common.h
>> index af41ad80ec21..5b4a042f60fb 100644
>> --- a/include/soc/tegra/common.h
>> +++ b/include/soc/tegra/common.h
>> @@ -39,4 +39,28 @@ devm_tegra_core_dev_init_opp_table(struct device *dev,
>> }
>> #endif
>>
>> +/*
>> + * This function should be invoked with the enabled runtime PM of the device
>> + * in order to initialize performance state properly. Most of Tegra devices
>> + * are assumed to be suspended at a probe time and GENPD require RPM to be
>> + * enabled to set up the rpm-resume state, otherwise device is active and
>> + * performance state is applied immediately. Note that it will initialize
>> + * OPP bandwidth if it's wired in a device-tree for this device, which is
>> + * undesirable for a suspended device.
>> + */
>> +static inline int
>> +devm_tegra_core_dev_init_opp_table_common(struct device *dev)
>> +{
>> + struct tegra_core_opp_params opp_params = {};
>> + int err;
>> +
>> + opp_params.init_state = true;
>> +
>> + err = devm_tegra_core_dev_init_opp_table(dev, &opp_params);
>> + if (err != -ENODEV)
>> + return err;
>> +
>> + return 0;
>> +}
>
> Just want to share a few thoughts around these functions.
>
> So, I assume it's fine to call
> devm_tegra_core_dev_init_opp_table_common() or
> devm_tegra_core_dev_init_opp_table() from consumer drivers during
> ->probe(), as long as those drivers are tegra specific, which I assume
> all are in the series!?

That is correct, all drivers are tegra-specific in this series. External
devices are attached to the internal SoC devices and this series is
about the SoC power management.

> My point is, a cross SoC consumer driver that needs to initiate OPP
> tables can get rather messy, if it would need to make one specific
> function call per SoC.
>
> That said, I hope we can tackle this as a separate/future problem, so
> the series can get merged as is.

Yes, as we already have seen, it's not an easy problem to make PD core
to handle it in a generic way. If there will be a similar demand from
other SoCs, then we may try to solve that problem again.

2021-10-01 20:03:29

by Dmitry Osipenko

[permalink] [raw]
Subject: Re: [PATCH v13 13/35] drm/tegra: gr2d: Support generic power domain and runtime PM

01.10.2021 17:55, Ulf Hansson пишет:
> On Fri, 1 Oct 2021 at 16:29, Dmitry Osipenko <[email protected]> wrote:
>>
>> 01.10.2021 16:39, Ulf Hansson пишет:
>>> On Mon, 27 Sept 2021 at 00:42, Dmitry Osipenko <[email protected]> wrote:
>>>>
>>>> Add runtime power management and support generic power domains.
>>>>
>>>> Tested-by: Peter Geis <[email protected]> # Ouya T30
>>>> Tested-by: Paul Fertser <[email protected]> # PAZ00 T20
>>>> Tested-by: Nicolas Chauvet <[email protected]> # PAZ00 T20 and TK1 T124
>>>> Tested-by: Matt Merhar <[email protected]> # Ouya T30
>>>> Signed-off-by: Dmitry Osipenko <[email protected]>
>>>> ---
>>>> drivers/gpu/drm/tegra/gr2d.c | 155 +++++++++++++++++++++++++++++++++--
>>>
>>> [...]
>>>
>>>> static int gr2d_remove(struct platform_device *pdev)
>>>> @@ -259,15 +312,101 @@ static int gr2d_remove(struct platform_device *pdev)
>>>> return err;
>>>> }
>>>>
>>>> + pm_runtime_dont_use_autosuspend(&pdev->dev);
>>>> + pm_runtime_disable(&pdev->dev);
>>>
>>> There is no guarantee that the ->runtime_suspend() has been invoked
>>> here, which means that clock may be left prepared/enabled beyond this
>>> point.
>>>
>>> I suggest you call pm_runtime_force_suspend(), instead of
>>> pm_runtime_disable(), to make sure that gets done.
>>
>> The pm_runtime_disable() performs the final synchronization, please see [1].
>>
>> [1]
>> https://elixir.bootlin.com/linux/v5.15-rc3/source/drivers/base/power/runtime.c#L1412
>
> pm_runtime_disable() end up calling _pm_runtime_barrier(), which calls
> cancel_work_sync() if dev->power.request_pending has been set.
>
> If the work that was punted to the pm_wq in rpm_idle() has not been
> started yet, we end up just canceling it. In other words, there are no
> guarantees it runs to completion.

You're right. Although, in a case of this particular patch, the syncing
is actually implicitly done by pm_runtime_dont_use_autosuspend().

But for drivers which don't use auto-suspend, there is no sync. This
looks like a disaster, it's a very common pattern for drivers to
'put+disable'.

> Moreover, use space may have bumped the usage count via sysfs for the
> device (pm_runtime_forbid()) to keep the device runtime resumed.

Right, this is also a disaster in a case of driver removal.

>> Calling pm_runtime_force_suspend() isn't correct because each 'enable'
>> must have the corresponding 'disable'. Hence there is no problem here.
>
> pm_runtime_force_suspend() calls pm_runtime_disable(), so I think that
> should be fine. No?

[adding Rafael]

Rafael, could you please explain how drivers are supposed to properly
suspend and disable RPM to cut off power and reset state that was
altered by the driver's resume callback? What we're missing? Is Ulf's
suggestion acceptable?

The RPM state of a device is getting reset on driver's removal, hence
all refcounts that were bumped by the rpm-resume callback of the device
driver will be screwed up if device is kept resumed after removal. I
just verified that it's true in practice.

2021-10-01 20:59:31

by Dmitry Osipenko

[permalink] [raw]
Subject: Re: [PATCH v13 06/35] clk: tegra: Support runtime PM and power domain

01.10.2021 15:32, Ulf Hansson пишет:
>> +static int tegra_clock_sync_pd_state(struct tegra_clk_device *clk_dev)
>> +{
>> + unsigned long rate;
>> + int ret = 0;
>> +
>> + mutex_lock(&clk_dev->lock);
>> +
>> + if (!pm_runtime_status_suspended(clk_dev->dev)) {
>> + rate = clk_hw_get_rate(clk_dev->hw);
>> + ret = tegra_clock_set_pd_state(clk_dev, rate);
> Don't we need to sync the performance state even when the device is
> runtime suspended?
>
> Perhaps the clock, via a child-clock for example, can get
> prepared/enabled (hence its device gets runtime resumed) before there
> is a clock rate update for it. Then there is no performance state set
> for it, right? Or maybe that isn't a problem?
>

Good catch! Older versions of this patch had a special handling for clk
enable/disable. I just forgot to update this function, it's now not a
problem to change performance state of a suspended device and it
actually needs to be done. I'll correct it, thanks!

2021-10-01 21:47:04

by Dmitry Osipenko

[permalink] [raw]
Subject: Re: [PATCH v13 14/35] drm/tegra: gr3d: Support generic power domain and runtime PM

01.10.2021 17:06, Ulf Hansson пишет:
> On Mon, 27 Sept 2021 at 00:42, Dmitry Osipenko <[email protected]> wrote:
>> Add runtime power management and support generic power domains.
>>
>> Tested-by: Peter Geis <[email protected]> # Ouya T30
>> Tested-by: Paul Fertser <[email protected]> # PAZ00 T20
>> Tested-by: Nicolas Chauvet <[email protected]> # PAZ00 T20 and TK1 T124
>> Tested-by: Matt Merhar <[email protected]> # Ouya T30
>> Signed-off-by: Dmitry Osipenko <[email protected]>
>> ---
>> drivers/gpu/drm/tegra/gr3d.c | 388 ++++++++++++++++++++++++++++++-----
> [...]

>
> I was looking for a call to dev_pm_opp_set_rate(), but couldn't find
> it. Isn't that needed when changing the rate of the clock?

That is another good catch! Previous versions of this patch were
changing the rate, while the current version not. So the
set_opp_helper() isn't needed for this patch anymore. It may become
needed sometime later, but not for this series. I'll remove it in the
next version, thanks!

2021-10-02 20:48:42

by Dmitry Osipenko

[permalink] [raw]
Subject: Re: [PATCH v13 06/35] clk: tegra: Support runtime PM and power domain

01.10.2021 15:32, Ulf Hansson пишет:
>> +static __maybe_unused int tegra_clock_pm_suspend(struct device *dev)
>> +{
>> + struct tegra_clk_device *clk_dev = dev_get_drvdata(dev);
>> +
>> + /*
>> + * Power management of the clock is entangled with the Tegra PMC
>> + * GENPD because PMC driver enables/disables clocks for toggling
>> + * of the PD's on/off state.
>> + *
>> + * The PMC GENPD is resumed in NOIRQ phase, before RPM of the clocks
>> + * becomes available, hence PMC can't use clocks at the early resume
>> + * phase if RPM is involved. For example when 3d clock is enabled,
>> + * it may enable the parent PLL clock that needs to be RPM-resumed.
>> + *
>> + * Secondly, the PLL clocks may be enabled by the low level suspend
>> + * code, so we need to assume that PLL is in enabled state during
>> + * suspend.
>> + *
>> + * We will keep PLLs and system clock resumed during suspend time.
>> + * All PLLs on all SoCs are low power and system clock is always-on,
>> + * so practically not much is changed here.
>> + */
>> +
>> + return clk_prepare(clk_dev->hw->clk);
> I am trying to understand, more exactly, what you intend to achieve
> with the clk_prepare() here. It looks a bit weird, to me. Can you try
> to elaborate a bit more on the use case?

The Tegra GENPD driver enable/disable clocks when domain is turned on.
This can't be done during early system resume, when domains are getting
turned on by the drivers core, because when clock is enabled, it's
getting prepared (RPM-resumed) and this preparation fails because
performance state of the clock goes up and it doesn't work during the
early resume time since I2C, which applies the state to hardware, is
suspended and can't work at that early time.

Secondly, Tegra has arch-specific low level assembly which touches
clocks during last phase of system suspend and in the beginning of
resume. Hence, clocks should stay prepared during suspend just because
technically clock should be prepared before it can be enabled.

> Is this rather about making sure that the clock's corresponding PM
> domain stays powered on during system suspend? In that case, I think
> there may be an alternative option....
>

This is not about domain staying powered on, this is about keeping the
performance state of the domain high during suspend.

2021-10-04 09:13:47

by Viresh Kumar

[permalink] [raw]
Subject: Re: [PATCH v13 00/35] NVIDIA Tegra power management patches for 5.16

On 27-09-21, 01:40, Dmitry Osipenko wrote:
> This series adds runtime PM support to Tegra drivers and enables core
> voltage scaling for Tegra20/30 SoCs, resolving overheating troubles.
>
> All patches in this series are interdependent and should go via Tegra tree.

So you don't need any OPP changes anymore ? I just came back from
vacation, don't know what you guys discussed in between :)

--
viresh

2021-10-04 10:25:44

by Viresh Kumar

[permalink] [raw]
Subject: Re: [PATCH v13 01/35] opp: Change type of dev_pm_opp_attach_genpd(names) argument

On 27-09-21, 01:40, Dmitry Osipenko wrote:
> Elements of the 'names' array are not changed by the code, constify them
> for consistency.
>
> Signed-off-by: Dmitry Osipenko <[email protected]>
> ---
> drivers/opp/core.c | 6 +++---
> include/linux/pm_opp.h | 8 ++++----
> 2 files changed, 7 insertions(+), 7 deletions(-)
>
> diff --git a/drivers/opp/core.c b/drivers/opp/core.c
> index 04b4691a8aac..3057beabd370 100644
> --- a/drivers/opp/core.c
> +++ b/drivers/opp/core.c
> @@ -2348,12 +2348,12 @@ static void _opp_detach_genpd(struct opp_table *opp_table)
> * "required-opps" are added in DT.
> */
> struct opp_table *dev_pm_opp_attach_genpd(struct device *dev,
> - const char **names, struct device ***virt_devs)
> + const char * const *names, struct device ***virt_devs)
> {
> struct opp_table *opp_table;
> struct device *virt_dev;
> int index = 0, ret = -EINVAL;
> - const char **name = names;
> + const char * const *name = names;
>
> opp_table = _add_opp_table(dev, false);
> if (IS_ERR(opp_table))
> @@ -2457,7 +2457,7 @@ static void devm_pm_opp_detach_genpd(void *data)
> *
> * Return: 0 on success and errorno otherwise.
> */
> -int devm_pm_opp_attach_genpd(struct device *dev, const char **names,
> +int devm_pm_opp_attach_genpd(struct device *dev, const char * const *names,
> struct device ***virt_devs)
> {
> struct opp_table *opp_table;
> diff --git a/include/linux/pm_opp.h b/include/linux/pm_opp.h
> index a95d6fdd20b6..879c138c7b8e 100644
> --- a/include/linux/pm_opp.h
> +++ b/include/linux/pm_opp.h
> @@ -156,9 +156,9 @@ int devm_pm_opp_set_clkname(struct device *dev, const char *name);
> struct opp_table *dev_pm_opp_register_set_opp_helper(struct device *dev, int (*set_opp)(struct dev_pm_set_opp_data *data));
> void dev_pm_opp_unregister_set_opp_helper(struct opp_table *opp_table);
> int devm_pm_opp_register_set_opp_helper(struct device *dev, int (*set_opp)(struct dev_pm_set_opp_data *data));
> -struct opp_table *dev_pm_opp_attach_genpd(struct device *dev, const char **names, struct device ***virt_devs);
> +struct opp_table *dev_pm_opp_attach_genpd(struct device *dev, const char * const *names, struct device ***virt_devs);
> void dev_pm_opp_detach_genpd(struct opp_table *opp_table);
> -int devm_pm_opp_attach_genpd(struct device *dev, const char **names, struct device ***virt_devs);
> +int devm_pm_opp_attach_genpd(struct device *dev, const char * const *names, struct device ***virt_devs);
> struct dev_pm_opp *dev_pm_opp_xlate_required_opp(struct opp_table *src_table, struct opp_table *dst_table, struct dev_pm_opp *src_opp);
> int dev_pm_opp_xlate_performance_state(struct opp_table *src_table, struct opp_table *dst_table, unsigned int pstate);
> int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq);
> @@ -376,7 +376,7 @@ static inline int devm_pm_opp_set_clkname(struct device *dev, const char *name)
> return -EOPNOTSUPP;
> }
>
> -static inline struct opp_table *dev_pm_opp_attach_genpd(struct device *dev, const char **names, struct device ***virt_devs)
> +static inline struct opp_table *dev_pm_opp_attach_genpd(struct device *dev, const char * const *names, struct device ***virt_devs)
> {
> return ERR_PTR(-EOPNOTSUPP);
> }
> @@ -384,7 +384,7 @@ static inline struct opp_table *dev_pm_opp_attach_genpd(struct device *dev, cons
> static inline void dev_pm_opp_detach_genpd(struct opp_table *opp_table) {}
>
> static inline int devm_pm_opp_attach_genpd(struct device *dev,
> - const char **names,
> + const char * const *names,
> struct device ***virt_devs)
> {
> return -EOPNOTSUPP;

Applied. Thanks.

--
viresh

2021-10-04 11:04:38

by Ulf Hansson

[permalink] [raw]
Subject: Re: [PATCH v13 13/35] drm/tegra: gr2d: Support generic power domain and runtime PM

On Fri, 1 Oct 2021 at 21:00, Dmitry Osipenko <[email protected]> wrote:
>
> 01.10.2021 17:55, Ulf Hansson пишет:
> > On Fri, 1 Oct 2021 at 16:29, Dmitry Osipenko <[email protected]> wrote:
> >>
> >> 01.10.2021 16:39, Ulf Hansson пишет:
> >>> On Mon, 27 Sept 2021 at 00:42, Dmitry Osipenko <[email protected]> wrote:
> >>>>
> >>>> Add runtime power management and support generic power domains.
> >>>>
> >>>> Tested-by: Peter Geis <[email protected]> # Ouya T30
> >>>> Tested-by: Paul Fertser <[email protected]> # PAZ00 T20
> >>>> Tested-by: Nicolas Chauvet <[email protected]> # PAZ00 T20 and TK1 T124
> >>>> Tested-by: Matt Merhar <[email protected]> # Ouya T30
> >>>> Signed-off-by: Dmitry Osipenko <[email protected]>
> >>>> ---
> >>>> drivers/gpu/drm/tegra/gr2d.c | 155 +++++++++++++++++++++++++++++++++--
> >>>
> >>> [...]
> >>>
> >>>> static int gr2d_remove(struct platform_device *pdev)
> >>>> @@ -259,15 +312,101 @@ static int gr2d_remove(struct platform_device *pdev)
> >>>> return err;
> >>>> }
> >>>>
> >>>> + pm_runtime_dont_use_autosuspend(&pdev->dev);
> >>>> + pm_runtime_disable(&pdev->dev);
> >>>
> >>> There is no guarantee that the ->runtime_suspend() has been invoked
> >>> here, which means that clock may be left prepared/enabled beyond this
> >>> point.
> >>>
> >>> I suggest you call pm_runtime_force_suspend(), instead of
> >>> pm_runtime_disable(), to make sure that gets done.
> >>
> >> The pm_runtime_disable() performs the final synchronization, please see [1].
> >>
> >> [1]
> >> https://elixir.bootlin.com/linux/v5.15-rc3/source/drivers/base/power/runtime.c#L1412
> >
> > pm_runtime_disable() end up calling _pm_runtime_barrier(), which calls
> > cancel_work_sync() if dev->power.request_pending has been set.
> >
> > If the work that was punted to the pm_wq in rpm_idle() has not been
> > started yet, we end up just canceling it. In other words, there are no
> > guarantees it runs to completion.
>
> You're right. Although, in a case of this particular patch, the syncing
> is actually implicitly done by pm_runtime_dont_use_autosuspend().
>
> But for drivers which don't use auto-suspend, there is no sync. This
> looks like a disaster, it's a very common pattern for drivers to
> 'put+disable'.
>
> > Moreover, use space may have bumped the usage count via sysfs for the
> > device (pm_runtime_forbid()) to keep the device runtime resumed.
>
> Right, this is also a disaster in a case of driver removal.
>
> >> Calling pm_runtime_force_suspend() isn't correct because each 'enable'
> >> must have the corresponding 'disable'. Hence there is no problem here.
> >
> > pm_runtime_force_suspend() calls pm_runtime_disable(), so I think that
> > should be fine. No?
>
> [adding Rafael]
>
> Rafael, could you please explain how drivers are supposed to properly
> suspend and disable RPM to cut off power and reset state that was
> altered by the driver's resume callback? What we're missing? Is Ulf's
> suggestion acceptable?
>
> The RPM state of a device is getting reset on driver's removal, hence
> all refcounts that were bumped by the rpm-resume callback of the device
> driver will be screwed up if device is kept resumed after removal. I
> just verified that it's true in practice.

Note that, what makes the Tegra drivers a bit special is that they are
always built with CONFIG_PM being set (selected from the "SoC"
Kconfig).

Therefore, pm_runtime_force_suspend() can work for some of these
cases. Using this, would potentially avoid the driver from having to
runtime resume the device in ->remove(), according to the below
generic sequence, which is used in many drivers.

pm_runtime_get_sync()
clk_disable_unprepare() (+ additional things to turn off the device)
pm_runtime_disable()
pm_runtime_put_noidle()

Kind regards
Uffe

2021-10-04 17:39:51

by Dmitry Osipenko

[permalink] [raw]
Subject: Re: [PATCH v13 00/35] NVIDIA Tegra power management patches for 5.16

04.10.2021 12:11, Viresh Kumar пишет:
> On 27-09-21, 01:40, Dmitry Osipenko wrote:
>> This series adds runtime PM support to Tegra drivers and enables core
>> voltage scaling for Tegra20/30 SoCs, resolving overheating troubles.
>>
>> All patches in this series are interdependent and should go via Tegra tree.
>
> So you don't need any OPP changes anymore ? I just came back from
> vacation, don't know what you guys discussed in between :)
>

We discussed it and decided that we don't need more OPP/domain core
changes. It's already good enough for the starter and making it all
absolutely ideal doesn't worth the effort for now.

2021-10-05 00:06:06

by Dmitry Osipenko

[permalink] [raw]
Subject: Re: [PATCH v13 13/35] drm/tegra: gr2d: Support generic power domain and runtime PM

04.10.2021 14:01, Ulf Hansson пишет:
> On Fri, 1 Oct 2021 at 21:00, Dmitry Osipenko <[email protected]> wrote:
>>
>> 01.10.2021 17:55, Ulf Hansson пишет:
>>> On Fri, 1 Oct 2021 at 16:29, Dmitry Osipenko <[email protected]> wrote:
>>>>
>>>> 01.10.2021 16:39, Ulf Hansson пишет:
>>>>> On Mon, 27 Sept 2021 at 00:42, Dmitry Osipenko <[email protected]> wrote:
>>>>>>
>>>>>> Add runtime power management and support generic power domains.
>>>>>>
>>>>>> Tested-by: Peter Geis <[email protected]> # Ouya T30
>>>>>> Tested-by: Paul Fertser <[email protected]> # PAZ00 T20
>>>>>> Tested-by: Nicolas Chauvet <[email protected]> # PAZ00 T20 and TK1 T124
>>>>>> Tested-by: Matt Merhar <[email protected]> # Ouya T30
>>>>>> Signed-off-by: Dmitry Osipenko <[email protected]>
>>>>>> ---
>>>>>> drivers/gpu/drm/tegra/gr2d.c | 155 +++++++++++++++++++++++++++++++++--
>>>>>
>>>>> [...]
>>>>>
>>>>>> static int gr2d_remove(struct platform_device *pdev)
>>>>>> @@ -259,15 +312,101 @@ static int gr2d_remove(struct platform_device *pdev)
>>>>>> return err;
>>>>>> }
>>>>>>
>>>>>> + pm_runtime_dont_use_autosuspend(&pdev->dev);
>>>>>> + pm_runtime_disable(&pdev->dev);
>>>>>
>>>>> There is no guarantee that the ->runtime_suspend() has been invoked
>>>>> here, which means that clock may be left prepared/enabled beyond this
>>>>> point.
>>>>>
>>>>> I suggest you call pm_runtime_force_suspend(), instead of
>>>>> pm_runtime_disable(), to make sure that gets done.
>>>>
>>>> The pm_runtime_disable() performs the final synchronization, please see [1].
>>>>
>>>> [1]
>>>> https://elixir.bootlin.com/linux/v5.15-rc3/source/drivers/base/power/runtime.c#L1412
>>>
>>> pm_runtime_disable() end up calling _pm_runtime_barrier(), which calls
>>> cancel_work_sync() if dev->power.request_pending has been set.
>>>
>>> If the work that was punted to the pm_wq in rpm_idle() has not been
>>> started yet, we end up just canceling it. In other words, there are no
>>> guarantees it runs to completion.
>>
>> You're right. Although, in a case of this particular patch, the syncing
>> is actually implicitly done by pm_runtime_dont_use_autosuspend().
>>
>> But for drivers which don't use auto-suspend, there is no sync. This
>> looks like a disaster, it's a very common pattern for drivers to
>> 'put+disable'.
>>
>>> Moreover, use space may have bumped the usage count via sysfs for the
>>> device (pm_runtime_forbid()) to keep the device runtime resumed.
>>
>> Right, this is also a disaster in a case of driver removal.
>>
>>>> Calling pm_runtime_force_suspend() isn't correct because each 'enable'
>>>> must have the corresponding 'disable'. Hence there is no problem here.
>>>
>>> pm_runtime_force_suspend() calls pm_runtime_disable(), so I think that
>>> should be fine. No?
>>
>> [adding Rafael]
>>
>> Rafael, could you please explain how drivers are supposed to properly
>> suspend and disable RPM to cut off power and reset state that was
>> altered by the driver's resume callback? What we're missing? Is Ulf's
>> suggestion acceptable?
>>
>> The RPM state of a device is getting reset on driver's removal, hence
>> all refcounts that were bumped by the rpm-resume callback of the device
>> driver will be screwed up if device is kept resumed after removal. I
>> just verified that it's true in practice.
>
> Note that, what makes the Tegra drivers a bit special is that they are
> always built with CONFIG_PM being set (selected from the "SoC"
> Kconfig).
>
> Therefore, pm_runtime_force_suspend() can work for some of these
> cases. Using this, would potentially avoid the driver from having to
> runtime resume the device in ->remove(), according to the below
> generic sequence, which is used in many drivers.
>
> pm_runtime_get_sync()
> clk_disable_unprepare() (+ additional things to turn off the device)
> pm_runtime_disable()
> pm_runtime_put_noidle()

It's not a problem to change this patchset. The problem is that if
you'll grep mainline for 'pm_runtime_disable', you will find that there
are a lot of drivers in a potential trouble.

I'm proposing that we should change pm_runtime_disable() to perform the
syncing with this oneliner:

diff --git a/drivers/base/power/runtime.c b/drivers/base/power/runtime.c
index ec94049442b9..5c9f28165824 100644
--- a/drivers/base/power/runtime.c
+++ b/drivers/base/power/runtime.c
@@ -1380,6 +1380,8 @@ EXPORT_SYMBOL_GPL(pm_runtime_barrier);
*/
void __pm_runtime_disable(struct device *dev, bool check_resume)
{
+ flush_work(&dev->power.work);
+
spin_lock_irq(&dev->power.lock);

if (dev->power.disable_depth > 0) {

Objections?

The sysfs rpm-forbid is a separate problem and it's less troublesome
since it requires root privileges. It's also not something that
userspace touches casually. For now I don't know what could be done
about it.

2021-10-05 08:48:37

by Ulf Hansson

[permalink] [raw]
Subject: Re: [PATCH v13 13/35] drm/tegra: gr2d: Support generic power domain and runtime PM

On Mon, 4 Oct 2021 at 17:57, Dmitry Osipenko <[email protected]> wrote:
>
> 04.10.2021 14:01, Ulf Hansson пишет:
> > On Fri, 1 Oct 2021 at 21:00, Dmitry Osipenko <[email protected]> wrote:
> >>
> >> 01.10.2021 17:55, Ulf Hansson пишет:
> >>> On Fri, 1 Oct 2021 at 16:29, Dmitry Osipenko <[email protected]> wrote:
> >>>>
> >>>> 01.10.2021 16:39, Ulf Hansson пишет:
> >>>>> On Mon, 27 Sept 2021 at 00:42, Dmitry Osipenko <[email protected]> wrote:
> >>>>>>
> >>>>>> Add runtime power management and support generic power domains.
> >>>>>>
> >>>>>> Tested-by: Peter Geis <[email protected]> # Ouya T30
> >>>>>> Tested-by: Paul Fertser <[email protected]> # PAZ00 T20
> >>>>>> Tested-by: Nicolas Chauvet <[email protected]> # PAZ00 T20 and TK1 T124
> >>>>>> Tested-by: Matt Merhar <[email protected]> # Ouya T30
> >>>>>> Signed-off-by: Dmitry Osipenko <[email protected]>
> >>>>>> ---
> >>>>>> drivers/gpu/drm/tegra/gr2d.c | 155 +++++++++++++++++++++++++++++++++--
> >>>>>
> >>>>> [...]
> >>>>>
> >>>>>> static int gr2d_remove(struct platform_device *pdev)
> >>>>>> @@ -259,15 +312,101 @@ static int gr2d_remove(struct platform_device *pdev)
> >>>>>> return err;
> >>>>>> }
> >>>>>>
> >>>>>> + pm_runtime_dont_use_autosuspend(&pdev->dev);
> >>>>>> + pm_runtime_disable(&pdev->dev);
> >>>>>
> >>>>> There is no guarantee that the ->runtime_suspend() has been invoked
> >>>>> here, which means that clock may be left prepared/enabled beyond this
> >>>>> point.
> >>>>>
> >>>>> I suggest you call pm_runtime_force_suspend(), instead of
> >>>>> pm_runtime_disable(), to make sure that gets done.
> >>>>
> >>>> The pm_runtime_disable() performs the final synchronization, please see [1].
> >>>>
> >>>> [1]
> >>>> https://elixir.bootlin.com/linux/v5.15-rc3/source/drivers/base/power/runtime.c#L1412
> >>>
> >>> pm_runtime_disable() end up calling _pm_runtime_barrier(), which calls
> >>> cancel_work_sync() if dev->power.request_pending has been set.
> >>>
> >>> If the work that was punted to the pm_wq in rpm_idle() has not been
> >>> started yet, we end up just canceling it. In other words, there are no
> >>> guarantees it runs to completion.
> >>
> >> You're right. Although, in a case of this particular patch, the syncing
> >> is actually implicitly done by pm_runtime_dont_use_autosuspend().
> >>
> >> But for drivers which don't use auto-suspend, there is no sync. This
> >> looks like a disaster, it's a very common pattern for drivers to
> >> 'put+disable'.
> >>
> >>> Moreover, use space may have bumped the usage count via sysfs for the
> >>> device (pm_runtime_forbid()) to keep the device runtime resumed.
> >>
> >> Right, this is also a disaster in a case of driver removal.
> >>
> >>>> Calling pm_runtime_force_suspend() isn't correct because each 'enable'
> >>>> must have the corresponding 'disable'. Hence there is no problem here.
> >>>
> >>> pm_runtime_force_suspend() calls pm_runtime_disable(), so I think that
> >>> should be fine. No?
> >>
> >> [adding Rafael]
> >>
> >> Rafael, could you please explain how drivers are supposed to properly
> >> suspend and disable RPM to cut off power and reset state that was
> >> altered by the driver's resume callback? What we're missing? Is Ulf's
> >> suggestion acceptable?
> >>
> >> The RPM state of a device is getting reset on driver's removal, hence
> >> all refcounts that were bumped by the rpm-resume callback of the device
> >> driver will be screwed up if device is kept resumed after removal. I
> >> just verified that it's true in practice.
> >
> > Note that, what makes the Tegra drivers a bit special is that they are
> > always built with CONFIG_PM being set (selected from the "SoC"
> > Kconfig).
> >
> > Therefore, pm_runtime_force_suspend() can work for some of these
> > cases. Using this, would potentially avoid the driver from having to
> > runtime resume the device in ->remove(), according to the below
> > generic sequence, which is used in many drivers.
> >
> > pm_runtime_get_sync()
> > clk_disable_unprepare() (+ additional things to turn off the device)
> > pm_runtime_disable()
> > pm_runtime_put_noidle()
>
> It's not a problem to change this patchset. The problem is that if
> you'll grep mainline for 'pm_runtime_disable', you will find that there
> are a lot of drivers in a potential trouble.

Let's start by fixing this patchset, please - then we can consider
what to do with the other cases separately.

>
> I'm proposing that we should change pm_runtime_disable() to perform the
> syncing with this oneliner:
>
> diff --git a/drivers/base/power/runtime.c b/drivers/base/power/runtime.c
> index ec94049442b9..5c9f28165824 100644
> --- a/drivers/base/power/runtime.c
> +++ b/drivers/base/power/runtime.c
> @@ -1380,6 +1380,8 @@ EXPORT_SYMBOL_GPL(pm_runtime_barrier);
> */
> void __pm_runtime_disable(struct device *dev, bool check_resume)
> {
> + flush_work(&dev->power.work);
> +

What about the latency this may introduce? I am not sure that is
acceptable here!?

> spin_lock_irq(&dev->power.lock);
>
> if (dev->power.disable_depth > 0) {
>
> Objections?
>
> The sysfs rpm-forbid is a separate problem and it's less troublesome
> since it requires root privileges. It's also not something that
> userspace touches casually. For now I don't know what could be done
> about it.

As I said, the common method to address this problem is to run the
following sequence:

pm_runtime_get_sync()
"power off the device"
pm_runtime_disable()
pm_runtime_put_noidle()

This works even if user space, via sysfs, has triggered a call to
pm_runtime_forbid(). Or doesn't it?

If you don't like it, pm_runtime_force_suspend() should work too, at
least for your cases, I believe.

Kind regards
Uffe

2021-10-05 13:13:37

by Ulf Hansson

[permalink] [raw]
Subject: Re: [PATCH v13 06/35] clk: tegra: Support runtime PM and power domain

On Sat, 2 Oct 2021 at 22:44, Dmitry Osipenko <[email protected]> wrote:
>
> 01.10.2021 15:32, Ulf Hansson пишет:
> >> +static __maybe_unused int tegra_clock_pm_suspend(struct device *dev)
> >> +{
> >> + struct tegra_clk_device *clk_dev = dev_get_drvdata(dev);
> >> +
> >> + /*
> >> + * Power management of the clock is entangled with the Tegra PMC
> >> + * GENPD because PMC driver enables/disables clocks for toggling
> >> + * of the PD's on/off state.
> >> + *
> >> + * The PMC GENPD is resumed in NOIRQ phase, before RPM of the clocks
> >> + * becomes available, hence PMC can't use clocks at the early resume
> >> + * phase if RPM is involved. For example when 3d clock is enabled,
> >> + * it may enable the parent PLL clock that needs to be RPM-resumed.
> >> + *
> >> + * Secondly, the PLL clocks may be enabled by the low level suspend
> >> + * code, so we need to assume that PLL is in enabled state during
> >> + * suspend.
> >> + *
> >> + * We will keep PLLs and system clock resumed during suspend time.
> >> + * All PLLs on all SoCs are low power and system clock is always-on,
> >> + * so practically not much is changed here.
> >> + */
> >> +
> >> + return clk_prepare(clk_dev->hw->clk);
> > I am trying to understand, more exactly, what you intend to achieve
> > with the clk_prepare() here. It looks a bit weird, to me. Can you try
> > to elaborate a bit more on the use case?
>
> The Tegra GENPD driver enable/disable clocks when domain is turned on.

Okay. I noticed that in tegra_genpd_power_on(). And the same clocks
are enabled/disabled also in tegra_genpd_power_off(), when powering
off the PM domain.

So I guess the problem kind of exists for tegra_genpd_power_off() too?

> This can't be done during early system resume, when domains are getting
> turned on by the drivers core, because when clock is enabled, it's
> getting prepared (RPM-resumed) and this preparation fails because
> performance state of the clock goes up and it doesn't work during the
> early resume time since I2C, which applies the state to hardware, is
> suspended and can't work at that early time.

This sounds complicated and I still don't quite follow all of it, sorry.

So, tegra_genpd_power_on() gets called from genpd_resume_noirq(), when
the first device of the attached devices to genpd gets resumed. And
vice versa for tegra_genpd_power_off() and genpd_suspend_noirq().

Are you saying that trying to enable/disable clocks from
tegra_genpd_power_on|off() in these paths doesn't work, because it
would also require the performance state to be changed, which would
fail because the I2C bus/driver is suspended?

>
> Secondly, Tegra has arch-specific low level assembly which touches
> clocks during last phase of system suspend and in the beginning of
> resume. Hence, clocks should stay prepared during suspend just because
> technically clock should be prepared before it can be enabled.

So the low level code is gating and ungating the clock behind the back
of the clock driver then? Why is that done like that, more exactly?

>
> > Is this rather about making sure that the clock's corresponding PM
> > domain stays powered on during system suspend? In that case, I think
> > there may be an alternative option....
> >
>
> This is not about domain staying powered on, this is about keeping the
> performance state of the domain high during suspend.

Right, so the PM domain managed in tegra_genpd_power_on|off() can
still be powered on/off, as long as the clock remains ungated?

Kind regards
Uffe

2021-10-05 17:18:27

by Dmitry Osipenko

[permalink] [raw]
Subject: Re: [PATCH v13 13/35] drm/tegra: gr2d: Support generic power domain and runtime PM

...
>> It's not a problem to change this patchset. The problem is that if
>> you'll grep mainline for 'pm_runtime_disable', you will find that there
>> are a lot of drivers in a potential trouble.
>
> Let's start by fixing this patchset, please - then we can consider
> what to do with the other cases separately.

Yeah, should be better to discuss it separately.

...
>> void __pm_runtime_disable(struct device *dev, bool check_resume)
>> {
>> + flush_work(&dev->power.work);
>> +
>
> What about the latency this may introduce? I am not sure that is
> acceptable here!?

I'm not aware about any code which relies on the original 'cancelling'
behaviour, perhaps Rafael should have more insight.

...
>> The sysfs rpm-forbid is a separate problem and it's less troublesome
>> since it requires root privileges. It's also not something that
>> userspace touches casually. For now I don't know what could be done
>> about it.
>
> As I said, the common method to address this problem is to run the
> following sequence:
>
> pm_runtime_get_sync()
> "power off the device"
> pm_runtime_disable()
> pm_runtime_put_noidle()
>
> This works even if user space, via sysfs, has triggered a call to
> pm_runtime_forbid(). Or doesn't it?
>
> If you don't like it, pm_runtime_force_suspend() should work too, at
> least for your cases, I believe.

I'll update the patches, thank you.

2021-10-05 22:20:49

by Dmitry Osipenko

[permalink] [raw]
Subject: Re: [PATCH v13 06/35] clk: tegra: Support runtime PM and power domain

05.10.2021 16:10, Ulf Hansson пишет:
> On Sat, 2 Oct 2021 at 22:44, Dmitry Osipenko <[email protected]> wrote:
>>
>> 01.10.2021 15:32, Ulf Hansson пишет:
>>>> +static __maybe_unused int tegra_clock_pm_suspend(struct device *dev)
>>>> +{
>>>> + struct tegra_clk_device *clk_dev = dev_get_drvdata(dev);
>>>> +
>>>> + /*
>>>> + * Power management of the clock is entangled with the Tegra PMC
>>>> + * GENPD because PMC driver enables/disables clocks for toggling
>>>> + * of the PD's on/off state.
>>>> + *
>>>> + * The PMC GENPD is resumed in NOIRQ phase, before RPM of the clocks
>>>> + * becomes available, hence PMC can't use clocks at the early resume
>>>> + * phase if RPM is involved. For example when 3d clock is enabled,
>>>> + * it may enable the parent PLL clock that needs to be RPM-resumed.
>>>> + *
>>>> + * Secondly, the PLL clocks may be enabled by the low level suspend
>>>> + * code, so we need to assume that PLL is in enabled state during
>>>> + * suspend.
>>>> + *
>>>> + * We will keep PLLs and system clock resumed during suspend time.
>>>> + * All PLLs on all SoCs are low power and system clock is always-on,
>>>> + * so practically not much is changed here.
>>>> + */
>>>> +
>>>> + return clk_prepare(clk_dev->hw->clk);
>>> I am trying to understand, more exactly, what you intend to achieve
>>> with the clk_prepare() here. It looks a bit weird, to me. Can you try
>>> to elaborate a bit more on the use case?
>>
>> The Tegra GENPD driver enable/disable clocks when domain is turned on.
>
> Okay. I noticed that in tegra_genpd_power_on(). And the same clocks
> are enabled/disabled also in tegra_genpd_power_off(), when powering
> off the PM domain.
>
> So I guess the problem kind of exists for tegra_genpd_power_off() too?

Both OFF/ON are affected by the same problem. If domain was already
turned OFF before genpd_suspend_noirq(), then the OFF problem isn't visible.

I reproduced the OFF problem by removing the clk prepare/unprepare from
the suspend/resume of the clk driver and making some extra changes to
clock tree topology and etc to trigger the problem on Nexus 7.

tegra-pmc 7000e400.pmc: failed to turn off PM domain heg: -13

I happens from genpd_suspend_noirq() -> tegra_genpd_power_off() -> clk
-> GENPD -> I2C -> runtime-pm.

-13 is EACCES, it comes from the runtime PM of I2C device. RPM is
prohibited/disabled during late (NOIRQ) suspend by the drivers core.

>> This can't be done during early system resume, when domains are getting
>> turned on by the drivers core, because when clock is enabled, it's
>> getting prepared (RPM-resumed) and this preparation fails because
>> performance state of the clock goes up and it doesn't work during the
>> early resume time since I2C, which applies the state to hardware, is
>> suspended and can't work at that early time.
>
> This sounds complicated and I still don't quite follow all of it, sorry.
>
> So, tegra_genpd_power_on() gets called from genpd_resume_noirq(), when
> the first device of the attached devices to genpd gets resumed. And
> vice versa for tegra_genpd_power_off() and genpd_suspend_noirq().
>
> Are you saying that trying to enable/disable clocks from
> tegra_genpd_power_on|off() in these paths doesn't work, because it
> would also require the performance state to be changed, which would
> fail because the I2C bus/driver is suspended?

Yes, but it's actually not I2C bus/driver that is suspended, it's
runtime PM that is unavailable during NOIRQ. The I2C driver itself is
suspended after domains are turned OFF and resumed before they are
enabled. It's just runtime PM API that is unavailable. I'm wondering if
this could be changed.

I'm also wondering if we could add some 'was_enabled' flag to GENPDs,
setting it by genpd_suspend_noirq() for the enabled domains, and then
powering-on GENPDs from genpd_resume_noirq() only if they were in the
enabled state during genpd_suspend_noirq() time. It actually puzzled me
for a quite long time why GENPD core enables domains unconditionally
during early resume. This should solve a part of the problem and it
makes suspend/resume a bit safer because there is a smaller chance to
crash hardware during suspend, at least it's easier to debug.

>> Secondly, Tegra has arch-specific low level assembly which touches
>> clocks during last phase of system suspend and in the beginning of
>> resume. Hence, clocks should stay prepared during suspend just because
>> technically clock should be prepared before it can be enabled.
>
> So the low level code is gating and ungating the clock behind the back
> of the clock driver then? Why is that done like that, more exactly?

I revisited that code again, and it shouldn't touch the clocks.
I changed that code to not toggle the clocks [1] sometime ago, but
forgot about it.

[1] https://git.kernel.org/linus/680ae4452

>>> Is this rather about making sure that the clock's corresponding PM
>>> domain stays powered on during system suspend? In that case, I think
>>> there may be an alternative option....
>>>
>>
>> This is not about domain staying powered on, this is about keeping the
>> performance state of the domain high during suspend.
>
> Right, so the PM domain managed in tegra_genpd_power_on|off() can
> still be powered on/off, as long as the clock remains ungated?

Not ungated, but prepared.

2021-10-05 22:44:21

by Dmitry Osipenko

[permalink] [raw]
Subject: Re: [PATCH v13 06/35] clk: tegra: Support runtime PM and power domain

06.10.2021 01:19, Dmitry Osipenko пишет:
...
> I reproduced the OFF problem by removing the clk prepare/unprepare from
> the suspend/resume of the clk driver and making some extra changes to
> clock tree topology and etc to trigger the problem on Nexus 7.
>
> tegra-pmc 7000e400.pmc: failed to turn off PM domain heg: -13
>
> It happens from genpd_suspend_noirq() -> tegra_genpd_power_off() -> clk
> -> GENPD -> I2C -> runtime-pm.
>
> -13 is EACCES, it comes from the runtime PM of I2C device. RPM is
> prohibited/disabled during late (NOIRQ) suspend by the drivers core.

My bad, I double-checked and it's not I2C RPM that is failing now, but
the clock's RPM [1], which is also unavailable during NOIRQ.

[1]
https://elixir.free-electrons.com/linux/v5.15-rc4/source/drivers/clk/clk.c#L116

Previously it was I2C RPM that was failing in a similar way, but code
changed a tad since that time.

2021-10-06 02:42:39

by Dmitry Osipenko

[permalink] [raw]
Subject: Re: [PATCH v13 06/35] clk: tegra: Support runtime PM and power domain

06.10.2021 01:43, Dmitry Osipenko пишет:
> 06.10.2021 01:19, Dmitry Osipenko пишет:
> ...
>> I reproduced the OFF problem by removing the clk prepare/unprepare from
>> the suspend/resume of the clk driver and making some extra changes to
>> clock tree topology and etc to trigger the problem on Nexus 7.
>>
>> tegra-pmc 7000e400.pmc: failed to turn off PM domain heg: -13
>>
>> It happens from genpd_suspend_noirq() -> tegra_genpd_power_off() -> clk
>> -> GENPD -> I2C -> runtime-pm.
>>
>> -13 is EACCES, it comes from the runtime PM of I2C device. RPM is
>> prohibited/disabled during late (NOIRQ) suspend by the drivers core.
>
> My bad, I double-checked and it's not I2C RPM that is failing now, but
> the clock's RPM [1], which is also unavailable during NOIRQ.
>
> [1]
> https://elixir.free-electrons.com/linux/v5.15-rc4/source/drivers/clk/clk.c#L116
>
> Previously it was I2C RPM that was failing in a similar way, but code
> changed a tad since that time.
>

Just in case, I checked that the suspension order isn't somehow the
source of the problem by adding links to device tree in order to always
suspend clocks after the rest of devices and still GENPD gets -EACCESS
from clk_pm_runtime_get().

RPM is disabled by dpm_suspend_late(), which is invoked before
dpm_suspend_noirq() [1]. Hence RPM is unavailable in NOIRQ phase in any
case.

[1]
https://elixir.bootlin.com/linux/v5.15-rc4/source/kernel/power/suspend.c#L399

2021-10-06 12:41:32

by Ulf Hansson

[permalink] [raw]
Subject: Re: [PATCH v13 06/35] clk: tegra: Support runtime PM and power domain

On Wed, 6 Oct 2021 at 00:19, Dmitry Osipenko <[email protected]> wrote:
>
> 05.10.2021 16:10, Ulf Hansson пишет:
> > On Sat, 2 Oct 2021 at 22:44, Dmitry Osipenko <[email protected]> wrote:
> >>
> >> 01.10.2021 15:32, Ulf Hansson пишет:
> >>>> +static __maybe_unused int tegra_clock_pm_suspend(struct device *dev)
> >>>> +{
> >>>> + struct tegra_clk_device *clk_dev = dev_get_drvdata(dev);
> >>>> +
> >>>> + /*
> >>>> + * Power management of the clock is entangled with the Tegra PMC
> >>>> + * GENPD because PMC driver enables/disables clocks for toggling
> >>>> + * of the PD's on/off state.
> >>>> + *
> >>>> + * The PMC GENPD is resumed in NOIRQ phase, before RPM of the clocks
> >>>> + * becomes available, hence PMC can't use clocks at the early resume
> >>>> + * phase if RPM is involved. For example when 3d clock is enabled,
> >>>> + * it may enable the parent PLL clock that needs to be RPM-resumed.
> >>>> + *
> >>>> + * Secondly, the PLL clocks may be enabled by the low level suspend
> >>>> + * code, so we need to assume that PLL is in enabled state during
> >>>> + * suspend.
> >>>> + *
> >>>> + * We will keep PLLs and system clock resumed during suspend time.
> >>>> + * All PLLs on all SoCs are low power and system clock is always-on,
> >>>> + * so practically not much is changed here.
> >>>> + */
> >>>> +
> >>>> + return clk_prepare(clk_dev->hw->clk);
> >>> I am trying to understand, more exactly, what you intend to achieve
> >>> with the clk_prepare() here. It looks a bit weird, to me. Can you try
> >>> to elaborate a bit more on the use case?
> >>
> >> The Tegra GENPD driver enable/disable clocks when domain is turned on.
> >
> > Okay. I noticed that in tegra_genpd_power_on(). And the same clocks
> > are enabled/disabled also in tegra_genpd_power_off(), when powering
> > off the PM domain.
> >
> > So I guess the problem kind of exists for tegra_genpd_power_off() too?
>
> Both OFF/ON are affected by the same problem. If domain was already
> turned OFF before genpd_suspend_noirq(), then the OFF problem isn't visible.
>
> I reproduced the OFF problem by removing the clk prepare/unprepare from
> the suspend/resume of the clk driver and making some extra changes to
> clock tree topology and etc to trigger the problem on Nexus 7.
>
> tegra-pmc 7000e400.pmc: failed to turn off PM domain heg: -13
>
> I happens from genpd_suspend_noirq() -> tegra_genpd_power_off() -> clk
> -> GENPD -> I2C -> runtime-pm.
>
> -13 is EACCES, it comes from the runtime PM of I2C device. RPM is
> prohibited/disabled during late (NOIRQ) suspend by the drivers core.

Thanks for the clarification!

>
> >> This can't be done during early system resume, when domains are getting
> >> turned on by the drivers core, because when clock is enabled, it's
> >> getting prepared (RPM-resumed) and this preparation fails because
> >> performance state of the clock goes up and it doesn't work during the
> >> early resume time since I2C, which applies the state to hardware, is
> >> suspended and can't work at that early time.
> >
> > This sounds complicated and I still don't quite follow all of it, sorry.
> >
> > So, tegra_genpd_power_on() gets called from genpd_resume_noirq(), when
> > the first device of the attached devices to genpd gets resumed. And
> > vice versa for tegra_genpd_power_off() and genpd_suspend_noirq().
> >
> > Are you saying that trying to enable/disable clocks from
> > tegra_genpd_power_on|off() in these paths doesn't work, because it
> > would also require the performance state to be changed, which would
> > fail because the I2C bus/driver is suspended?
>
> Yes, but it's actually not I2C bus/driver that is suspended, it's
> runtime PM that is unavailable during NOIRQ. The I2C driver itself is
> suspended after domains are turned OFF and resumed before they are
> enabled. It's just runtime PM API that is unavailable. I'm wondering if
> this could be changed.

In principle what you ask for, is if we can avoid calling
__pm_runtime_disable() in __device_suspend_late() (and vice versa in
device_resume_early()).

I think the short answer is no, at least from a generic point of view.
Maybe we can figure out a way to allow this on a per device basis, as
an opt-in solution. I am not sure what Rafael would think about this,
let's see.

Another option to address the problem is already available to use for
these kinds of cases. This would be to add also a pair of
->suspend|resume() callbacks to I2C driver. Along the lines of the
below.

diff --git a/drivers/i2c/busses/i2c-tegra.c b/drivers/i2c/busses/i2c-tegra.c
index c883044715f3..589bf872ab25 100644
--- a/drivers/i2c/busses/i2c-tegra.c
+++ b/drivers/i2c/busses/i2c-tegra.c
@@ -1918,6 +1918,7 @@ static int __maybe_unused
tegra_i2c_resume(struct device *dev)
}

static const struct dev_pm_ops tegra_i2c_pm = {
+ SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_put_noidle, pm_runtime_get_sync)
SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(tegra_i2c_suspend, tegra_i2c_resume)
SET_RUNTIME_PM_OPS(tegra_i2c_runtime_suspend, tegra_i2c_runtime_resume,
NULL)

In this way, the device would already be runtime resumed, if there is
call to pm_runtime_get_sync() from the clock framework due to the
clk_prepare|unprepare() being called. If that also turns out to happen
*after* runtime PM has been disabled for the device, the call to
pm_runtime_get_sync() would still succeed (returning 1, see
rpm_resume()), rather than a negative error code.

Yes, we may end up runtime resuming the device during system suspend,
even if it turns out not to be needed. Although, that doesn't seem to
be the case for the Tegra I2C driver, right?

>
> I'm also wondering if we could add some 'was_enabled' flag to GENPDs,
> setting it by genpd_suspend_noirq() for the enabled domains, and then
> powering-on GENPDs from genpd_resume_noirq() only if they were in the
> enabled state during genpd_suspend_noirq() time. It actually puzzled me
> for a quite long time why GENPD core enables domains unconditionally
> during early resume. This should solve a part of the problem and it
> makes suspend/resume a bit safer because there is a smaller chance to
> crash hardware during suspend, at least it's easier to debug.

Just because the PM domain was already off at genpd_suspend_noirq(),
doesn't mean that it can stay powered off at genpd_resume_noirq(). At
least as is today.

The main reason why genpd_resume_noirq() powers on the PM domain, is
because it's not possible for the consumer drivers to rely on runtime
PM to do it (because runtime PM has been disabled by the PM core).

>
> >> Secondly, Tegra has arch-specific low level assembly which touches
> >> clocks during last phase of system suspend and in the beginning of
> >> resume. Hence, clocks should stay prepared during suspend just because
> >> technically clock should be prepared before it can be enabled.
> >
> > So the low level code is gating and ungating the clock behind the back
> > of the clock driver then? Why is that done like that, more exactly?
>
> I revisited that code again, and it shouldn't touch the clocks.
> I changed that code to not toggle the clocks [1] sometime ago, but
> forgot about it.
>
> [1] https://git.kernel.org/linus/680ae4452
>
> >>> Is this rather about making sure that the clock's corresponding PM
> >>> domain stays powered on during system suspend? In that case, I think
> >>> there may be an alternative option....
> >>>
> >>
> >> This is not about domain staying powered on, this is about keeping the
> >> performance state of the domain high during suspend.
> >
> > Right, so the PM domain managed in tegra_genpd_power_on|off() can
> > still be powered on/off, as long as the clock remains ungated?
>
> Not ungated, but prepared.

Okay, thanks for clarifying!

In summary, it sounds like you should be able to fix this problem in
the I2C driver as I suggested above. If that works, that seems much
better.

Moreover, it would leave the clocks gated/unprepared when the system
is fully suspended, which I guess is better from an energy point of
view?

Kind regards
Uffe

2021-10-06 12:46:26

by Ulf Hansson

[permalink] [raw]
Subject: Re: [PATCH v13 06/35] clk: tegra: Support runtime PM and power domain

On Wed, 6 Oct 2021 at 00:43, Dmitry Osipenko <[email protected]> wrote:
>
> 06.10.2021 01:19, Dmitry Osipenko пишет:
> ...
> > I reproduced the OFF problem by removing the clk prepare/unprepare from
> > the suspend/resume of the clk driver and making some extra changes to
> > clock tree topology and etc to trigger the problem on Nexus 7.
> >
> > tegra-pmc 7000e400.pmc: failed to turn off PM domain heg: -13
> >
> > It happens from genpd_suspend_noirq() -> tegra_genpd_power_off() -> clk
> > -> GENPD -> I2C -> runtime-pm.
> >
> > -13 is EACCES, it comes from the runtime PM of I2C device. RPM is
> > prohibited/disabled during late (NOIRQ) suspend by the drivers core.
>
> My bad, I double-checked and it's not I2C RPM that is failing now, but
> the clock's RPM [1], which is also unavailable during NOIRQ.

Yes, that sounds reasonable.

You would then need a similar patch for the tegra clock driver as I
suggested for tegra I2C driver. That should solve the problem, I
think.

>
> [1]
> https://elixir.free-electrons.com/linux/v5.15-rc4/source/drivers/clk/clk.c#L116
>
> Previously it was I2C RPM that was failing in a similar way, but code
> changed a tad since that time.

Alright. In any case, as long as the devices gets suspended in the
correct order, I think it should be fine to cook a patch along the
lines of what I suggest for the I2C driver as well.

It should work, I think. Although, maybe you want to avoid runtime
resuming the I2C device, unless it's the device belonging to the PMIC
interface, if there is a way to distinguish that for the driver.

Kind regards
Uffe

2021-10-06 21:16:41

by Dmitry Osipenko

[permalink] [raw]
Subject: Re: [PATCH v13 06/35] clk: tegra: Support runtime PM and power domain

06.10.2021 15:43, Ulf Hansson пишет:
> On Wed, 6 Oct 2021 at 00:43, Dmitry Osipenko <[email protected]> wrote:
>>
>> 06.10.2021 01:19, Dmitry Osipenko пишет:
>> ...
>>> I reproduced the OFF problem by removing the clk prepare/unprepare from
>>> the suspend/resume of the clk driver and making some extra changes to
>>> clock tree topology and etc to trigger the problem on Nexus 7.
>>>
>>> tegra-pmc 7000e400.pmc: failed to turn off PM domain heg: -13
>>>
>>> It happens from genpd_suspend_noirq() -> tegra_genpd_power_off() -> clk
>>> -> GENPD -> I2C -> runtime-pm.
>>>
>>> -13 is EACCES, it comes from the runtime PM of I2C device. RPM is
>>> prohibited/disabled during late (NOIRQ) suspend by the drivers core.
>>
>> My bad, I double-checked and it's not I2C RPM that is failing now, but
>> the clock's RPM [1], which is also unavailable during NOIRQ.
>
> Yes, that sounds reasonable.
>
> You would then need a similar patch for the tegra clock driver as I
> suggested for tegra I2C driver. That should solve the problem, I
> think.
>
>>
>> [1]
>> https://elixir.free-electrons.com/linux/v5.15-rc4/source/drivers/clk/clk.c#L116
>>
>> Previously it was I2C RPM that was failing in a similar way, but code
>> changed a tad since that time.
>
> Alright. In any case, as long as the devices gets suspended in the
> correct order, I think it should be fine to cook a patch along the
> lines of what I suggest for the I2C driver as well.
>
> It should work, I think. Although, maybe you want to avoid runtime
> resuming the I2C device, unless it's the device belonging to the PMIC
> interface, if there is a way to distinguish that for the driver.

Ulf, thank you very much for the suggestions! I was thinking about this
all once again and concluded that the simplest variant will be to just
remove the suspend ops from the clk driver since neither of PLLs require
high voltage. We now have voltage bumped to a nominal level during
suspend by Tegra's regulator-coupler driver and it's much higher than
voltage needed by PLLs. So the problem I was trying to work around
doesn't really exist anymore.

2021-10-06 21:27:23

by Dmitry Osipenko

[permalink] [raw]
Subject: Re: [PATCH v13 06/35] clk: tegra: Support runtime PM and power domain

06.10.2021 15:38, Ulf Hansson пишет:
>> I'm also wondering if we could add some 'was_enabled' flag to GENPDs,
>> setting it by genpd_suspend_noirq() for the enabled domains, and then
>> powering-on GENPDs from genpd_resume_noirq() only if they were in the
>> enabled state during genpd_suspend_noirq() time. It actually puzzled me
>> for a quite long time why GENPD core enables domains unconditionally
>> during early resume. This should solve a part of the problem and it
>> makes suspend/resume a bit safer because there is a smaller chance to
>> crash hardware during suspend, at least it's easier to debug.
> Just because the PM domain was already off at genpd_suspend_noirq(),
> doesn't mean that it can stay powered off at genpd_resume_noirq(). At
> least as is today.
>
> The main reason why genpd_resume_noirq() powers on the PM domain, is
> because it's not possible for the consumer drivers to rely on runtime
> PM to do it (because runtime PM has been disabled by the PM core).

At least Tegra doesn't need to have domains force-resumed. This should
be a platform-specific behaviour. We may add a new flag for that, I
suppose. I'll try to keep this in mind for a future improvement. Thank
you for the clarification.

2021-10-06 22:18:39

by Dmitry Osipenko

[permalink] [raw]
Subject: Re: [PATCH v13 06/35] clk: tegra: Support runtime PM and power domain

06.10.2021 15:38, Ulf Hansson пишет:
> In principle what you ask for, is if we can avoid calling
> __pm_runtime_disable() in __device_suspend_late() (and vice versa in
> device_resume_early()).
>
> I think the short answer is no, at least from a generic point of view.
> Maybe we can figure out a way to allow this on a per device basis, as
> an opt-in solution. I am not sure what Rafael would think about this,
> let's see.
>
> Another option to address the problem is already available to use for
> these kinds of cases. This would be to add also a pair of
> ->suspend|resume() callbacks to I2C driver. Along the lines of the
> below.
>
> diff --git a/drivers/i2c/busses/i2c-tegra.c b/drivers/i2c/busses/i2c-tegra.c
> index c883044715f3..589bf872ab25 100644
> --- a/drivers/i2c/busses/i2c-tegra.c
> +++ b/drivers/i2c/busses/i2c-tegra.c
> @@ -1918,6 +1918,7 @@ static int __maybe_unused
> tegra_i2c_resume(struct device *dev)
> }
>
> static const struct dev_pm_ops tegra_i2c_pm = {
> + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_put_noidle, pm_runtime_get_sync)
> SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(tegra_i2c_suspend, tegra_i2c_resume)
> SET_RUNTIME_PM_OPS(tegra_i2c_runtime_suspend, tegra_i2c_runtime_resume,
> NULL)
>
> In this way, the device would already be runtime resumed, if there is
> call to pm_runtime_get_sync() from the clock framework due to the
> clk_prepare|unprepare() being called. If that also turns out to happen
> *after* runtime PM has been disabled for the device, the call to
> pm_runtime_get_sync() would still succeed (returning 1, see
> rpm_resume()), rather than a negative error code.
>
> Yes, we may end up runtime resuming the device during system suspend,
> even if it turns out not to be needed. Although, that doesn't seem to
> be the case for the Tegra I2C driver, right?

Tegra I2C will turn off clocks on suspend regardless of RPM state.
Overall, it's a plausible solution, thank you!

As I said in the other reply, I'll simply remove the suspend ops from
clk driver, they are not needed anymore. The problem is gone.

2021-10-06 22:29:31

by Dmitry Osipenko

[permalink] [raw]
Subject: Re: [PATCH v13 06/35] clk: tegra: Support runtime PM and power domain

06.10.2021 15:38, Ulf Hansson пишет:
>>> Right, so the PM domain managed in tegra_genpd_power_on|off() can
>>> still be powered on/off, as long as the clock remains ungated?
>> Not ungated, but prepared.
> Okay, thanks for clarifying!
>
> In summary, it sounds like you should be able to fix this problem in
> the I2C driver as I suggested above. If that works, that seems much
> better.

I'll try this variant, thank you.

> Moreover, it would leave the clocks gated/unprepared when the system
> is fully suspended, which I guess is better from an energy point of
> view?

The clocks are kept gated, it wasn't a problem. The problem was that
clocks were needed to be enabled temporarily. In order to enable a
clock, it needs to be prepared first. When clock is prepared, it resumes
clock's device RPM.

Keeping clocks prepared shouldn't make a noticeable difference from the
energy POV since clocks are gated. It's only voltage that is kept high,
but we need to keep it high during suspend anyways in order to resume
successfully. The hardware is mostly gated during suspend, depending on
suspend mode, so the power consumption difference is negligible. At
least I haven't seen any problems, battery doesn't drain during suspend.

2021-10-06 22:37:23

by Dmitry Osipenko

[permalink] [raw]
Subject: Re: [PATCH v13 06/35] clk: tegra: Support runtime PM and power domain

07.10.2021 00:14, Dmitry Osipenko пишет:
> 06.10.2021 15:43, Ulf Hansson пишет:
>> On Wed, 6 Oct 2021 at 00:43, Dmitry Osipenko <[email protected]> wrote:
>>>
>>> 06.10.2021 01:19, Dmitry Osipenko пишет:
>>> ...
>>>> I reproduced the OFF problem by removing the clk prepare/unprepare from
>>>> the suspend/resume of the clk driver and making some extra changes to
>>>> clock tree topology and etc to trigger the problem on Nexus 7.
>>>>
>>>> tegra-pmc 7000e400.pmc: failed to turn off PM domain heg: -13
>>>>
>>>> It happens from genpd_suspend_noirq() -> tegra_genpd_power_off() -> clk
>>>> -> GENPD -> I2C -> runtime-pm.
>>>>
>>>> -13 is EACCES, it comes from the runtime PM of I2C device. RPM is
>>>> prohibited/disabled during late (NOIRQ) suspend by the drivers core.
>>>
>>> My bad, I double-checked and it's not I2C RPM that is failing now, but
>>> the clock's RPM [1], which is also unavailable during NOIRQ.
>>
>> Yes, that sounds reasonable.
>>
>> You would then need a similar patch for the tegra clock driver as I
>> suggested for tegra I2C driver. That should solve the problem, I
>> think.
>>
>>>
>>> [1]
>>> https://elixir.free-electrons.com/linux/v5.15-rc4/source/drivers/clk/clk.c#L116
>>>
>>> Previously it was I2C RPM that was failing in a similar way, but code
>>> changed a tad since that time.
>>
>> Alright. In any case, as long as the devices gets suspended in the
>> correct order, I think it should be fine to cook a patch along the
>> lines of what I suggest for the I2C driver as well.
>>
>> It should work, I think. Although, maybe you want to avoid runtime
>> resuming the I2C device, unless it's the device belonging to the PMIC
>> interface, if there is a way to distinguish that for the driver.
>
> Ulf, thank you very much for the suggestions! I was thinking about this
> all once again and concluded that the simplest variant will be to just
> remove the suspend ops from the clk driver since neither of PLLs require
> high voltage. We now have voltage bumped to a nominal level during
> suspend by Tegra's regulator-coupler driver and it's much higher than
> voltage needed by PLLs. So the problem I was trying to work around
> doesn't really exist anymore.

I hurried a bit with the conclusion, keep forgetting that I need to
change the clock tree in order to test it all properly :/ It's not fixed
yet.

2021-10-06 23:41:38

by Dmitry Osipenko

[permalink] [raw]
Subject: Re: [PATCH v13 06/35] clk: tegra: Support runtime PM and power domain

07.10.2021 01:01, Dmitry Osipenko пишет:
> 07.10.2021 00:14, Dmitry Osipenko пишет:
>> 06.10.2021 15:43, Ulf Hansson пишет:
>>> On Wed, 6 Oct 2021 at 00:43, Dmitry Osipenko <[email protected]> wrote:
>>>>
>>>> 06.10.2021 01:19, Dmitry Osipenko пишет:
>>>> ...
>>>>> I reproduced the OFF problem by removing the clk prepare/unprepare from
>>>>> the suspend/resume of the clk driver and making some extra changes to
>>>>> clock tree topology and etc to trigger the problem on Nexus 7.
>>>>>
>>>>> tegra-pmc 7000e400.pmc: failed to turn off PM domain heg: -13
>>>>>
>>>>> It happens from genpd_suspend_noirq() -> tegra_genpd_power_off() -> clk
>>>>> -> GENPD -> I2C -> runtime-pm.
>>>>>
>>>>> -13 is EACCES, it comes from the runtime PM of I2C device. RPM is
>>>>> prohibited/disabled during late (NOIRQ) suspend by the drivers core.
>>>>
>>>> My bad, I double-checked and it's not I2C RPM that is failing now, but
>>>> the clock's RPM [1], which is also unavailable during NOIRQ.
>>>
>>> Yes, that sounds reasonable.
>>>
>>> You would then need a similar patch for the tegra clock driver as I
>>> suggested for tegra I2C driver. That should solve the problem, I
>>> think.
>>>
>>>>
>>>> [1]
>>>> https://elixir.free-electrons.com/linux/v5.15-rc4/source/drivers/clk/clk.c#L116
>>>>
>>>> Previously it was I2C RPM that was failing in a similar way, but code
>>>> changed a tad since that time.
>>>
>>> Alright. In any case, as long as the devices gets suspended in the
>>> correct order, I think it should be fine to cook a patch along the
>>> lines of what I suggest for the I2C driver as well.
>>>
>>> It should work, I think. Although, maybe you want to avoid runtime
>>> resuming the I2C device, unless it's the device belonging to the PMIC
>>> interface, if there is a way to distinguish that for the driver.
>>
>> Ulf, thank you very much for the suggestions! I was thinking about this
>> all once again and concluded that the simplest variant will be to just
>> remove the suspend ops from the clk driver since neither of PLLs require
>> high voltage. We now have voltage bumped to a nominal level during
>> suspend by Tegra's regulator-coupler driver and it's much higher than
>> voltage needed by PLLs. So the problem I was trying to work around
>> doesn't really exist anymore.
>
> I hurried a bit with the conclusion, keep forgetting that I need to
> change the clock tree in order to test it all properly :/ It's not fixed
> yet.
>

Please let me iterate once again. The problem we currently have is that
clock may be enabled during NOIRQ time. In order to enable clock, it
needs to be prepared. In order to prepare clock, the clock's device
needs to be runtime-resumed. The runtime PM is unavailable at the NOIRQ
time.

To solve this problem we need to prepare clock beforehand.

The clock will stay prepared during suspend, but this is not a problem
since all the clocks we care about don't require high voltage and
voltage is guaranteed to be bumped high during suspend by Tegra's
regulator-coupler driver anyways.

So everything we need to do is to keep clocks prepared. There are two
options how to do that:

[1] this patch which explicitly prepares clocks using clk API.

[2] Use runtime PM API, like this:

static const struct dev_pm_ops tegra_clock_pm = {
SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_resume_and_get, pm_runtime_put)
};

Ulf, are you now okay with the current variant [1] of the patch or you
prefer the second [2] option more?

2021-10-07 09:50:13

by Ulf Hansson

[permalink] [raw]
Subject: Re: [PATCH v13 06/35] clk: tegra: Support runtime PM and power domain

On Thu, 7 Oct 2021 at 01:21, Dmitry Osipenko <[email protected]> wrote:
>
> 07.10.2021 01:01, Dmitry Osipenko пишет:
> > 07.10.2021 00:14, Dmitry Osipenko пишет:
> >> 06.10.2021 15:43, Ulf Hansson пишет:
> >>> On Wed, 6 Oct 2021 at 00:43, Dmitry Osipenko <[email protected]> wrote:
> >>>>
> >>>> 06.10.2021 01:19, Dmitry Osipenko пишет:
> >>>> ...
> >>>>> I reproduced the OFF problem by removing the clk prepare/unprepare from
> >>>>> the suspend/resume of the clk driver and making some extra changes to
> >>>>> clock tree topology and etc to trigger the problem on Nexus 7.
> >>>>>
> >>>>> tegra-pmc 7000e400.pmc: failed to turn off PM domain heg: -13
> >>>>>
> >>>>> It happens from genpd_suspend_noirq() -> tegra_genpd_power_off() -> clk
> >>>>> -> GENPD -> I2C -> runtime-pm.
> >>>>>
> >>>>> -13 is EACCES, it comes from the runtime PM of I2C device. RPM is
> >>>>> prohibited/disabled during late (NOIRQ) suspend by the drivers core.
> >>>>
> >>>> My bad, I double-checked and it's not I2C RPM that is failing now, but
> >>>> the clock's RPM [1], which is also unavailable during NOIRQ.
> >>>
> >>> Yes, that sounds reasonable.
> >>>
> >>> You would then need a similar patch for the tegra clock driver as I
> >>> suggested for tegra I2C driver. That should solve the problem, I
> >>> think.
> >>>
> >>>>
> >>>> [1]
> >>>> https://elixir.free-electrons.com/linux/v5.15-rc4/source/drivers/clk/clk.c#L116
> >>>>
> >>>> Previously it was I2C RPM that was failing in a similar way, but code
> >>>> changed a tad since that time.
> >>>
> >>> Alright. In any case, as long as the devices gets suspended in the
> >>> correct order, I think it should be fine to cook a patch along the
> >>> lines of what I suggest for the I2C driver as well.
> >>>
> >>> It should work, I think. Although, maybe you want to avoid runtime
> >>> resuming the I2C device, unless it's the device belonging to the PMIC
> >>> interface, if there is a way to distinguish that for the driver.
> >>
> >> Ulf, thank you very much for the suggestions! I was thinking about this
> >> all once again and concluded that the simplest variant will be to just
> >> remove the suspend ops from the clk driver since neither of PLLs require
> >> high voltage. We now have voltage bumped to a nominal level during
> >> suspend by Tegra's regulator-coupler driver and it's much higher than
> >> voltage needed by PLLs. So the problem I was trying to work around
> >> doesn't really exist anymore.
> >
> > I hurried a bit with the conclusion, keep forgetting that I need to
> > change the clock tree in order to test it all properly :/ It's not fixed
> > yet.
> >
>
> Please let me iterate once again. The problem we currently have is that
> clock may be enabled during NOIRQ time. In order to enable clock, it
> needs to be prepared. In order to prepare clock, the clock's device
> needs to be runtime-resumed. The runtime PM is unavailable at the NOIRQ
> time.
>
> To solve this problem we need to prepare clock beforehand.
>
> The clock will stay prepared during suspend, but this is not a problem
> since all the clocks we care about don't require high voltage and
> voltage is guaranteed to be bumped high during suspend by Tegra's
> regulator-coupler driver anyways.
>
> So everything we need to do is to keep clocks prepared. There are two
> options how to do that:
>
> [1] this patch which explicitly prepares clocks using clk API.
>
> [2] Use runtime PM API, like this:
>
> static const struct dev_pm_ops tegra_clock_pm = {
> SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_resume_and_get, pm_runtime_put)
> };
>
> Ulf, are you now okay with the current variant [1] of the patch or you
> prefer the second [2] option more?

I prefer option [2]. The clock_prepare|unprepare() thingy in option
[1], looks more like an odd workaround to me.

Does that make sense to you as well?

Kind regards
Uffe

2021-10-07 10:38:03

by Dmitry Osipenko

[permalink] [raw]
Subject: Re: [PATCH v13 06/35] clk: tegra: Support runtime PM and power domain

07.10.2021 12:18, Ulf Hansson пишет:
>> Please let me iterate once again. The problem we currently have is that
>> clock may be enabled during NOIRQ time. In order to enable clock, it
>> needs to be prepared. In order to prepare clock, the clock's device
>> needs to be runtime-resumed. The runtime PM is unavailable at the NOIRQ
>> time.
>>
>> To solve this problem we need to prepare clock beforehand.
>>
>> The clock will stay prepared during suspend, but this is not a problem
>> since all the clocks we care about don't require high voltage and
>> voltage is guaranteed to be bumped high during suspend by Tegra's
>> regulator-coupler driver anyways.
>>
>> So everything we need to do is to keep clocks prepared. There are two
>> options how to do that:
>>
>> [1] this patch which explicitly prepares clocks using clk API.
>>
>> [2] Use runtime PM API, like this:
>>
>> static const struct dev_pm_ops tegra_clock_pm = {
>> SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_resume_and_get, pm_runtime_put)
>> };
>>
>> Ulf, are you now okay with the current variant [1] of the patch or you
>> prefer the second [2] option more?
> I prefer option [2]. The clock_prepare|unprepare() thingy in option
> [1], looks more like an odd workaround to me.
>
> Does that make sense to you as well?

I don't have a strong preference since both variants give the same
effect. I'll keep testing option [2] and will use it in the next version
if no problem will be found. Thank you!

2021-10-18 03:36:46

by Dmitry Osipenko

[permalink] [raw]
Subject: Re: [PATCH v13 11/35] drm/tegra: dc: Support OPP and SoC core voltage scaling

01.10.2021 16:27, Ulf Hansson пишет:
> On Mon, 27 Sept 2021 at 00:42, Dmitry Osipenko <[email protected]> wrote:
>>
>> Add OPP and SoC core voltage scaling support to the display controller
>> driver. This is required for enabling system-wide DVFS on pre-Tegra186
>> SoCs.
>>
>> Tested-by: Peter Geis <[email protected]> # Ouya T30
>> Tested-by: Paul Fertser <[email protected]> # PAZ00 T20
>> Tested-by: Nicolas Chauvet <[email protected]> # PAZ00 T20 and TK1 T124
>> Tested-by: Matt Merhar <[email protected]> # Ouya T30
>> Signed-off-by: Dmitry Osipenko <[email protected]>
>> ---
>> drivers/gpu/drm/tegra/dc.c | 74 ++++++++++++++++++++++++++++++++++++++
>> drivers/gpu/drm/tegra/dc.h | 2 ++
>> 2 files changed, 76 insertions(+)
>>
>> diff --git a/drivers/gpu/drm/tegra/dc.c b/drivers/gpu/drm/tegra/dc.c
>> index a29d64f87563..d4047a14e2b6 100644
>> --- a/drivers/gpu/drm/tegra/dc.c
>> +++ b/drivers/gpu/drm/tegra/dc.c
>> @@ -11,9 +11,12 @@
>> #include <linux/interconnect.h>
>> #include <linux/module.h>
>> #include <linux/of_device.h>
>> +#include <linux/pm_domain.h>
>> +#include <linux/pm_opp.h>
>> #include <linux/pm_runtime.h>
>> #include <linux/reset.h>
>>
>> +#include <soc/tegra/common.h>
>> #include <soc/tegra/pmc.h>
>>
>> #include <drm/drm_atomic.h>
>> @@ -1762,6 +1765,47 @@ int tegra_dc_state_setup_clock(struct tegra_dc *dc,
>> return 0;
>> }
>>
>> +static void tegra_dc_update_voltage_state(struct tegra_dc *dc,
>> + struct tegra_dc_state *state)
>> +{
>> + unsigned long rate, pstate;
>> + struct dev_pm_opp *opp;
>> + int err;
>> +
>> + if (!dc->has_opp_table)
>> + return;
>> +
>> + /* calculate actual pixel clock rate which depends on internal divider */
>> + rate = DIV_ROUND_UP(clk_get_rate(dc->clk) * 2, state->div + 2);
>> +
>> + /* find suitable OPP for the rate */
>> + opp = dev_pm_opp_find_freq_ceil(dc->dev, &rate);
>> +
>> + if (opp == ERR_PTR(-ERANGE))
>> + opp = dev_pm_opp_find_freq_floor(dc->dev, &rate);
>> +
>> + if (IS_ERR(opp)) {
>> + dev_err(dc->dev, "failed to find OPP for %luHz: %pe\n",
>> + rate, opp);
>> + return;
>> + }
>> +
>> + pstate = dev_pm_opp_get_required_pstate(opp, 0);
>> + dev_pm_opp_put(opp);
>> +
>> + /*
>> + * The minimum core voltage depends on the pixel clock rate (which
>> + * depends on internal clock divider of the CRTC) and not on the
>> + * rate of the display controller clock. This is why we're not using
>> + * dev_pm_opp_set_rate() API and instead controlling the power domain
>> + * directly.
>> + */
>> + err = dev_pm_genpd_set_performance_state(dc->dev, pstate);
>> + if (err)
>> + dev_err(dc->dev, "failed to set power domain state to %lu: %d\n",
>> + pstate, err);
>
> Yeah, the above code looks very similar to the code I pointed to in
> patch6. Perhaps we need to discuss with Viresh, whether it makes sense
> to fold in a patch adding an opp helper function after all, to avoid
> the open coding.
>
> Viresh?

I'll keep it open-coded for now. This code is specific to Tegra because
normally ceil error shouldn't fall back to the floor, but for Tegra it's
expected to happen and it's a normal condition.

2021-10-18 03:40:41

by Dmitry Osipenko

[permalink] [raw]
Subject: Re: [PATCH v13 20/35] mtd: rawnand: tegra: Add runtime PM and OPP support

01.10.2021 18:01, Ulf Hansson пишет:
> On Fri, 1 Oct 2021 at 16:35, Dmitry Osipenko <[email protected]> wrote:
>>
>> 01.10.2021 17:24, Ulf Hansson пишет:
>>> On Mon, 27 Sept 2021 at 00:42, Dmitry Osipenko <[email protected]> wrote:
>>>>
>>>> The NAND on Tegra belongs to the core power domain and we're going to
>>>> enable GENPD support for the core domain. Now NAND must be resumed using
>>>> runtime PM API in order to initialize the NAND power state. Add runtime PM
>>>> and OPP support to the NAND driver.
>>>>
>>>> Acked-by: Miquel Raynal <[email protected]>
>>>> Signed-off-by: Dmitry Osipenko <[email protected]>
>>>> ---
>>>> drivers/mtd/nand/raw/tegra_nand.c | 55 ++++++++++++++++++++++++++-----
>>>> 1 file changed, 47 insertions(+), 8 deletions(-)
>>>>
>>>> diff --git a/drivers/mtd/nand/raw/tegra_nand.c b/drivers/mtd/nand/raw/tegra_nand.c
>>>> index 32431bbe69b8..098fcc9cb9df 100644
>>>> --- a/drivers/mtd/nand/raw/tegra_nand.c
>>>> +++ b/drivers/mtd/nand/raw/tegra_nand.c
>>>> @@ -17,8 +17,11 @@
>>>> #include <linux/mtd/rawnand.h>
>>>> #include <linux/of.h>
>>>> #include <linux/platform_device.h>
>>>> +#include <linux/pm_runtime.h>
>>>> #include <linux/reset.h>
>>>>
>>>> +#include <soc/tegra/common.h>
>>>> +
>>>> #define COMMAND 0x00
>>>> #define COMMAND_GO BIT(31)
>>>> #define COMMAND_CLE BIT(30)
>>>> @@ -1151,6 +1154,7 @@ static int tegra_nand_probe(struct platform_device *pdev)
>>>> return -ENOMEM;
>>>>
>>>> ctrl->dev = &pdev->dev;
>>>> + platform_set_drvdata(pdev, ctrl);
>>>> nand_controller_init(&ctrl->controller);
>>>> ctrl->controller.ops = &tegra_nand_controller_ops;
>>>>
>>>> @@ -1166,14 +1170,22 @@ static int tegra_nand_probe(struct platform_device *pdev)
>>>> if (IS_ERR(ctrl->clk))
>>>> return PTR_ERR(ctrl->clk);
>>>>
>>>> - err = clk_prepare_enable(ctrl->clk);
>>>> + err = devm_pm_runtime_enable(&pdev->dev);
>>>> + if (err)
>>>> + return err;
>>>> +
>>>> + err = devm_tegra_core_dev_init_opp_table_common(&pdev->dev);
>>>> + if (err)
>>>> + return err;
>>>> +
>>>> + err = pm_runtime_resume_and_get(&pdev->dev);
>>>> if (err)
>>>> return err;
>>>>
>>>> err = reset_control_reset(rst);
>>>> if (err) {
>>>> dev_err(ctrl->dev, "Failed to reset HW: %d\n", err);
>>>> - goto err_disable_clk;
>>>> + goto err_put_pm;
>>>> }
>>>>
>>>> writel_relaxed(HWSTATUS_CMD_DEFAULT, ctrl->regs + HWSTATUS_CMD);
>>>> @@ -1188,21 +1200,19 @@ static int tegra_nand_probe(struct platform_device *pdev)
>>>> dev_name(&pdev->dev), ctrl);
>>>> if (err) {
>>>> dev_err(ctrl->dev, "Failed to get IRQ: %d\n", err);
>>>> - goto err_disable_clk;
>>>> + goto err_put_pm;
>>>> }
>>>>
>>>> writel_relaxed(DMA_MST_CTRL_IS_DONE, ctrl->regs + DMA_MST_CTRL);
>>>>
>>>> err = tegra_nand_chips_init(ctrl->dev, ctrl);
>>>> if (err)
>>>> - goto err_disable_clk;
>>>> -
>>>> - platform_set_drvdata(pdev, ctrl);
>>>> + goto err_put_pm;
>>>>
>>>
>>> There is no corresponding call pm_runtime_put() here. Is it
>>> intentional to always leave the device runtime resumed after ->probe()
>>> has succeeded?
>>>
>>> I noticed you included some comments about this for some other
>>> drivers, as those needed more tweaks. Is that also the case for this
>>> driver?
>>
>> Could you please clarify? There is pm_runtime_put() in both probe-error
>> and remove() code paths here.
>
> I was not considering the error path of ->probe() (or ->remove()), but
> was rather thinking about when ->probe() completes successfully. Then
> you keep the device runtime resumed, because you have called
> pm_runtime_resume_and_get() for it.
>
> Shouldn't you have a corresponding pm_runtime_put() in ->probe(),
> allowing it to be runtime suspended, until the device is really needed
> later on. No?

This driver doesn't support active power management. I don't have Tegra
hardware that uses NAND storage for testing, so it's up to somebody else
to implement dynamic power management. NAND doesn't require high
voltages, so it's fine to keep the old driver behaviour by keeping
hardware resumed since the probe time.

2021-10-19 11:43:22

by Ulf Hansson

[permalink] [raw]
Subject: Re: [PATCH v13 20/35] mtd: rawnand: tegra: Add runtime PM and OPP support

On Sun, 17 Oct 2021 at 10:38, Dmitry Osipenko <[email protected]> wrote:
>
> 01.10.2021 18:01, Ulf Hansson пишет:
> > On Fri, 1 Oct 2021 at 16:35, Dmitry Osipenko <[email protected]> wrote:
> >>
> >> 01.10.2021 17:24, Ulf Hansson пишет:
> >>> On Mon, 27 Sept 2021 at 00:42, Dmitry Osipenko <[email protected]> wrote:
> >>>>
> >>>> The NAND on Tegra belongs to the core power domain and we're going to
> >>>> enable GENPD support for the core domain. Now NAND must be resumed using
> >>>> runtime PM API in order to initialize the NAND power state. Add runtime PM
> >>>> and OPP support to the NAND driver.
> >>>>
> >>>> Acked-by: Miquel Raynal <[email protected]>
> >>>> Signed-off-by: Dmitry Osipenko <[email protected]>
> >>>> ---
> >>>> drivers/mtd/nand/raw/tegra_nand.c | 55 ++++++++++++++++++++++++++-----
> >>>> 1 file changed, 47 insertions(+), 8 deletions(-)
> >>>>
> >>>> diff --git a/drivers/mtd/nand/raw/tegra_nand.c b/drivers/mtd/nand/raw/tegra_nand.c
> >>>> index 32431bbe69b8..098fcc9cb9df 100644
> >>>> --- a/drivers/mtd/nand/raw/tegra_nand.c
> >>>> +++ b/drivers/mtd/nand/raw/tegra_nand.c
> >>>> @@ -17,8 +17,11 @@
> >>>> #include <linux/mtd/rawnand.h>
> >>>> #include <linux/of.h>
> >>>> #include <linux/platform_device.h>
> >>>> +#include <linux/pm_runtime.h>
> >>>> #include <linux/reset.h>
> >>>>
> >>>> +#include <soc/tegra/common.h>
> >>>> +
> >>>> #define COMMAND 0x00
> >>>> #define COMMAND_GO BIT(31)
> >>>> #define COMMAND_CLE BIT(30)
> >>>> @@ -1151,6 +1154,7 @@ static int tegra_nand_probe(struct platform_device *pdev)
> >>>> return -ENOMEM;
> >>>>
> >>>> ctrl->dev = &pdev->dev;
> >>>> + platform_set_drvdata(pdev, ctrl);
> >>>> nand_controller_init(&ctrl->controller);
> >>>> ctrl->controller.ops = &tegra_nand_controller_ops;
> >>>>
> >>>> @@ -1166,14 +1170,22 @@ static int tegra_nand_probe(struct platform_device *pdev)
> >>>> if (IS_ERR(ctrl->clk))
> >>>> return PTR_ERR(ctrl->clk);
> >>>>
> >>>> - err = clk_prepare_enable(ctrl->clk);
> >>>> + err = devm_pm_runtime_enable(&pdev->dev);
> >>>> + if (err)
> >>>> + return err;
> >>>> +
> >>>> + err = devm_tegra_core_dev_init_opp_table_common(&pdev->dev);
> >>>> + if (err)
> >>>> + return err;
> >>>> +
> >>>> + err = pm_runtime_resume_and_get(&pdev->dev);
> >>>> if (err)
> >>>> return err;
> >>>>
> >>>> err = reset_control_reset(rst);
> >>>> if (err) {
> >>>> dev_err(ctrl->dev, "Failed to reset HW: %d\n", err);
> >>>> - goto err_disable_clk;
> >>>> + goto err_put_pm;
> >>>> }
> >>>>
> >>>> writel_relaxed(HWSTATUS_CMD_DEFAULT, ctrl->regs + HWSTATUS_CMD);
> >>>> @@ -1188,21 +1200,19 @@ static int tegra_nand_probe(struct platform_device *pdev)
> >>>> dev_name(&pdev->dev), ctrl);
> >>>> if (err) {
> >>>> dev_err(ctrl->dev, "Failed to get IRQ: %d\n", err);
> >>>> - goto err_disable_clk;
> >>>> + goto err_put_pm;
> >>>> }
> >>>>
> >>>> writel_relaxed(DMA_MST_CTRL_IS_DONE, ctrl->regs + DMA_MST_CTRL);
> >>>>
> >>>> err = tegra_nand_chips_init(ctrl->dev, ctrl);
> >>>> if (err)
> >>>> - goto err_disable_clk;
> >>>> -
> >>>> - platform_set_drvdata(pdev, ctrl);
> >>>> + goto err_put_pm;
> >>>>
> >>>
> >>> There is no corresponding call pm_runtime_put() here. Is it
> >>> intentional to always leave the device runtime resumed after ->probe()
> >>> has succeeded?
> >>>
> >>> I noticed you included some comments about this for some other
> >>> drivers, as those needed more tweaks. Is that also the case for this
> >>> driver?
> >>
> >> Could you please clarify? There is pm_runtime_put() in both probe-error
> >> and remove() code paths here.
> >
> > I was not considering the error path of ->probe() (or ->remove()), but
> > was rather thinking about when ->probe() completes successfully. Then
> > you keep the device runtime resumed, because you have called
> > pm_runtime_resume_and_get() for it.
> >
> > Shouldn't you have a corresponding pm_runtime_put() in ->probe(),
> > allowing it to be runtime suspended, until the device is really needed
> > later on. No?
>
> This driver doesn't support active power management. I don't have Tegra
> hardware that uses NAND storage for testing, so it's up to somebody else
> to implement dynamic power management. NAND doesn't require high
> voltages, so it's fine to keep the old driver behaviour by keeping
> hardware resumed since the probe time.

Alright, fair enough and thanks for clarifying!

Kind regards
Uffe