2020-12-17 18:10:04

by Dmitry Osipenko

[permalink] [raw]
Subject: [PATCH v2 00/48] Introduce core voltage scaling for NVIDIA Tegra20/30 SoCs

Introduce core voltage scaling for NVIDIA Tegra20/30 SoCs, which reduces
power consumption and heating of the Tegra chips. Tegra SoC has multiple
hardware units which belong to a core power domain of the SoC and share
the core voltage. The voltage must be selected in accordance to a minimum
requirement of every core hardware unit.

The minimum core voltage requirement depends on:

1. Clock enable state of a hardware unit.
2. Clock frequency.
3. Unit's internal idling/active state.

This series is tested on Acer A500 (T20), AC100 (T20), Nexus 7 (T30),
Ouya (T30), TK1 (T124) and some others. I also added voltage scaling to
the Ventana (T20) and Cardhu (T30) boards which are tested by NVIDIA's CI
farm. Tegra30 is now couple degrees cooler on Nexus 7 and stays cool on
Ouya (instead of becoming burning hot) while system is idling. It should
be possible to improve this further by implementing a more advanced power
management features for the kernel drivers.

The DVFS support is opt-in for all boards, meaning that older DTBs will
continue to work like they did it before this series. It should be possible
to easily add the core voltage scaling support for Tegra114+ SoCs based on
this grounding work later on, if anyone will want to implement it.

Changelog:

v2: - Replaced Core voltage regulator with a Core power domain. The voltage
control is now done using GENPD API. This was suggested by Ulf Hansson.

- Added basic runtime PM and GENPD support to 2d, 3d, host1x and clk
drivers.

- Added new core-power-domain and clk-device drivers. Some high-freq
PLLs and clocks require a higher minimum core voltage and the new
clk-device driver manages the voltage for these clocks based on
the clock state.

- Moved voltage scaling entirely to the new clk-device driver for devices
which don't require advanced power management, like PWM for example.

- Added devm_tegra_core_dev_init_opp_table() common helper which sets up
OPP table for Tegra drivers.

- Added resource-managed version for OPP API functions, as it was
discussed previously in the comments to v1.

- Added new APIs, features and fixed various bugs related to voltage
scaling and power management done via GENPD API.

Dmitry Osipenko (48):
dt-bindings: memory: tegra20: emc: Replace core regulator with power
domain
dt-bindings: memory: tegra30: emc: Replace core regulator with power
domain
dt-bindings: memory: tegra124: emc: Replace core regulator with power
domain
dt-bindings: host1x: Document OPP and power domain properties
media: dt: bindings: tegra-vde: Document OPP and power domain
properties
dt-bindings: clock: tegra: Document clocks sub-node
dt-bindings: arm: tegra: Add binding for core power domain
regulator: Make regulator_sync_voltage() usable by coupled regulators
opp: Add dev_pm_opp_sync_regulators()
opp: Add dev_pm_opp_set_voltage()
opp: Add dev_pm_opp_find_level_ceil()
opp: Add dev_pm_opp_get_required_pstate()
opp: Add resource-managed versions of OPP API functions
opp: Filter out OPPs based on availability of a required-OPP
opp: Support set_opp() customization without requiring to use
regulators
opp: Handle missing OPP table in dev_pm_opp_xlate_performance_state()
opp: Correct debug message in _opp_add_static_v2()
opp: Print OPP level in debug message of _opp_add_static_v2()
opp: Fix adding OPP entries in a wrong order if rate is unavailable
PM: domains: Make set_performance_state() callback optional
PM: domains: Add "performance" column to debug summary
soc/tegra: pmc: Fix imbalanced clock disabling in error code path
soc/tegra: pmc: Pulse resets after removing power clamp
soc/tegra: pmc: Ensure that clock rates aren't too high
soc/tegra: pmc: Print out domain name when reset fails to acquire
soc/tegra: Add devm_tegra_core_dev_init_opp_table()
soc/tegra: Add CONFIG_SOC_TEGRA_COMMON and select PM_OPP by default
soc/tegra: Introduce core power domain driver
soc/tegra: pmc: Link domains to the parent Core domain
soc/tegra: regulators: Fix locking up when voltage-spread is out of
range
soc/tegra: regulators: Support Core domain state syncing
clk: tegra: Support runtime PM, power domain and OPP
gpu: host1x: Add host1x_channel_stop()
gpu: host1x: Support power management
drm/tegra: dc: Support OPP and SoC core voltage scaling
drm/tegra: gr2d: Correct swapped device-tree compatibles
drm/tegra: gr2d: Support OPP and power management
drm/tegra: g3d: Support OPP and power management
drm/tegra: vic: Stop channel before suspending
media: staging: tegra-vde: Support OPP and generic power domain
memory: tegra20-emc: Use devm_tegra_core_dev_init_opp_table()
memory: tegra30-emc: Use devm_tegra_core_dev_init_opp_table()
ARM: tegra: Add OPP tables and power domains to Tegra20 device-tree
ARM: tegra: Add OPP tables and power domains to Tegra30 device-tree
ARM: tegra: acer-a500: Enable core voltage scaling
ARM: tegra: ventana: Enable core voltage scaling
ARM: tegra: ventana: Support CPU voltage scaling and thermal
throttling
ARM: tegra: cardhu: Support CPU voltage scaling and thermal throttling

.../arm/tegra/nvidia,tegra20-core-domain.yaml | 48 +
.../bindings/clock/nvidia,tegra20-car.txt | 26 +
.../bindings/clock/nvidia,tegra30-car.txt | 26 +
.../display/tegra/nvidia,tegra20-host1x.txt | 49 +
.../bindings/media/nvidia,tegra-vde.txt | 12 +
.../nvidia,tegra124-emc.yaml | 6 +-
.../memory-controllers/nvidia,tegra20-emc.txt | 4 +-
.../nvidia,tegra30-emc.yaml | 6 +-
.../boot/dts/tegra20-acer-a500-picasso.dts | 8 +-
arch/arm/boot/dts/tegra20-colibri.dtsi | 6 +-
arch/arm/boot/dts/tegra20-harmony.dts | 6 +-
arch/arm/boot/dts/tegra20-paz00.dts | 46 +-
.../arm/boot/dts/tegra20-peripherals-opp.dtsi | 941 +++++++++++
arch/arm/boot/dts/tegra20-seaboard.dts | 6 +-
arch/arm/boot/dts/tegra20-tamonten.dtsi | 6 +-
arch/arm/boot/dts/tegra20-trimslice.dts | 12 +
arch/arm/boot/dts/tegra20-ventana.dts | 78 +-
arch/arm/boot/dts/tegra20.dtsi | 220 +++
.../tegra30-asus-nexus7-grouper-common.dtsi | 4 +
arch/arm/boot/dts/tegra30-beaver.dts | 4 +
arch/arm/boot/dts/tegra30-cardhu.dtsi | 81 +-
arch/arm/boot/dts/tegra30-colibri.dtsi | 20 +-
arch/arm/boot/dts/tegra30-ouya.dts | 4 +
.../arm/boot/dts/tegra30-peripherals-opp.dtsi | 1412 +++++++++++++++++
arch/arm/boot/dts/tegra30.dtsi | 358 +++++
drivers/base/power/domain.c | 33 +-
drivers/clk/tegra/Makefile | 1 +
drivers/clk/tegra/clk-device.c | 222 +++
drivers/clk/tegra/clk-divider.c | 2 +-
drivers/clk/tegra/clk-periph-gate.c | 2 +-
drivers/clk/tegra/clk-periph.c | 2 +-
drivers/clk/tegra/clk-pll.c | 2 +-
drivers/clk/tegra/clk-super.c | 4 +-
drivers/clk/tegra/clk-tegra-periph.c | 140 +-
drivers/clk/tegra/clk-tegra114.c | 1 +
drivers/clk/tegra/clk-tegra124.c | 1 +
drivers/clk/tegra/clk-tegra20-emc.c | 2 +-
drivers/clk/tegra/clk-tegra20.c | 123 +-
drivers/clk/tegra/clk-tegra210.c | 1 +
drivers/clk/tegra/clk-tegra30.c | 133 +-
drivers/clk/tegra/clk.c | 89 ++
drivers/clk/tegra/clk.h | 7 +
drivers/gpu/drm/tegra/dc.c | 66 +-
drivers/gpu/drm/tegra/gr2d.c | 77 +-
drivers/gpu/drm/tegra/gr3d.c | 264 ++-
drivers/gpu/drm/tegra/vic.c | 15 +
drivers/gpu/host1x/channel.c | 8 +
drivers/gpu/host1x/dev.c | 102 +-
drivers/memory/tegra/tegra20-emc.c | 57 +-
drivers/memory/tegra/tegra30-emc.c | 57 +-
drivers/opp/core.c | 390 ++++-
drivers/opp/of.c | 34 +-
drivers/opp/opp.h | 2 +-
drivers/regulator/core.c | 6 +
drivers/soc/tegra/Kconfig | 19 +
drivers/soc/tegra/Makefile | 1 +
drivers/soc/tegra/common.c | 137 ++
drivers/soc/tegra/core-power-domain.c | 125 ++
drivers/soc/tegra/pmc.c | 122 +-
drivers/soc/tegra/regulators-tegra20.c | 19 +-
drivers/soc/tegra/regulators-tegra30.c | 20 +-
drivers/staging/media/tegra-vde/vde.c | 63 +-
include/linux/host1x.h | 1 +
include/linux/pm_opp.h | 81 +
include/soc/tegra/common.h | 41 +
65 files changed, 5458 insertions(+), 403 deletions(-)
create mode 100644 Documentation/devicetree/bindings/arm/tegra/nvidia,tegra20-core-domain.yaml
create mode 100644 drivers/clk/tegra/clk-device.c
create mode 100644 drivers/soc/tegra/core-power-domain.c

--
2.29.2


2020-12-17 18:10:26

by Dmitry Osipenko

[permalink] [raw]
Subject: [PATCH v2 34/48] gpu: host1x: Support power management

Add suspend/resume and generic power domain support to the Host1x driver.
This is required for enabling system-wide DVFS and supporting dynamic
power management using a generic power domain.

Tested-by: Peter Geis <[email protected]>
Tested-by: Nicolas Chauvet <[email protected]>
Signed-off-by: Dmitry Osipenko <[email protected]>
---
drivers/gpu/host1x/dev.c | 102 ++++++++++++++++++++++++++++++++++-----
1 file changed, 91 insertions(+), 11 deletions(-)

diff --git a/drivers/gpu/host1x/dev.c b/drivers/gpu/host1x/dev.c
index d0ebb70e2fdd..c1525cffe7b1 100644
--- a/drivers/gpu/host1x/dev.c
+++ b/drivers/gpu/host1x/dev.c
@@ -12,6 +12,7 @@
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/of.h>
+#include <linux/pm_runtime.h>
#include <linux/slab.h>

#define CREATE_TRACE_POINTS
@@ -417,7 +418,7 @@ static int host1x_probe(struct platform_device *pdev)
return err;
}

- host->rst = devm_reset_control_get(&pdev->dev, "host1x");
+ host->rst = devm_reset_control_get_exclusive_released(&pdev->dev, "host1x");
if (IS_ERR(host->rst)) {
err = PTR_ERR(host->rst);
dev_err(&pdev->dev, "failed to get reset: %d\n", err);
@@ -437,16 +438,15 @@ 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;
- }
+ pm_runtime_enable(&pdev->dev);
+ err = pm_runtime_get_sync(&pdev->dev);
+ if (err < 0)
+ goto rpm_disable;

err = reset_control_deassert(host->rst);
if (err < 0) {
dev_err(&pdev->dev, "failed to deassert reset: %d\n", err);
- goto unprepare_disable;
+ goto rpm_disable;
}

err = host1x_syncpt_init(host);
@@ -485,9 +485,10 @@ static int host1x_probe(struct platform_device *pdev)
host1x_syncpt_deinit(host);
reset_assert:
reset_control_assert(host->rst);
-unprepare_disable:
- clk_disable_unprepare(host->clk);
-free_channels:
+rpm_disable:
+ pm_runtime_put(&pdev->dev);
+ pm_runtime_disable(&pdev->dev);
+
host1x_channel_list_free(&host->channel_list);
iommu_exit:
host1x_iommu_exit(host);
@@ -504,16 +505,95 @@ static int host1x_remove(struct platform_device *pdev)
host1x_intr_deinit(host);
host1x_syncpt_deinit(host);
reset_control_assert(host->rst);
- clk_disable_unprepare(host->clk);
+ pm_runtime_put(&pdev->dev);
+ pm_runtime_disable(&pdev->dev);
host1x_iommu_exit(host);

return 0;
}

+static int __maybe_unused host1x_runtime_suspend(struct device *dev)
+{
+ struct host1x *host = dev_get_drvdata(dev);
+
+ clk_disable_unprepare(host->clk);
+ reset_control_release(host->rst);
+
+ return 0;
+}
+
+static int __maybe_unused host1x_runtime_resume(struct device *dev)
+{
+ struct host1x *host = dev_get_drvdata(dev);
+ int err;
+
+ err = reset_control_acquire(host->rst);
+ 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;
+ }
+
+ return 0;
+
+release_reset:
+ reset_control_release(host->rst);
+
+ return err;
+}
+
+static __maybe_unused int host1x_suspend(struct device *dev)
+{
+ struct host1x *host = dev_get_drvdata(dev);
+ int err;
+
+ host1x_syncpt_save(host);
+
+ err = pm_runtime_force_suspend(dev);
+ if (err < 0)
+ return err;
+
+ return 0;
+}
+
+static __maybe_unused int host1x_resume(struct device *dev)
+{
+ struct host1x *host = dev_get_drvdata(dev);
+ struct host1x_channel *channel;
+ unsigned int index;
+ int err;
+
+ err = pm_runtime_force_resume(dev);
+ if (err < 0)
+ return err;
+
+ host1x_syncpt_restore(host);
+
+ for_each_set_bit(index, host->channel_list.allocated_channels,
+ host->info->nb_channels) {
+ channel = &host->channel_list.channels[index];
+ host1x_hw_channel_init(host, channel, channel->id);
+ }
+
+ return 0;
+}
+
+static const struct dev_pm_ops host1x_pm = {
+ SET_RUNTIME_PM_OPS(host1x_runtime_suspend, host1x_runtime_resume,
+ NULL)
+ SET_SYSTEM_SLEEP_PM_OPS(host1x_suspend, host1x_resume)
+};
+
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,
--
2.29.2

2020-12-17 18:10:28

by Dmitry Osipenko

[permalink] [raw]
Subject: [PATCH v2 04/48] 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.

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 34d993338453..43c38f01fd77 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.29.2

2020-12-17 18:10:43

by Dmitry Osipenko

[permalink] [raw]
Subject: [PATCH v2 28/48] soc/tegra: Introduce core power domain driver

NVIDIA Tegra SoCs have multiple power domains, each domain corresponds
to an external SoC power rail. Core power domain covers vast majority of
hardware blocks within a Tegra SoC. The voltage of a power domain should
be set to a value which satisfies all devices within a power domain. Add
driver for the core power domain in order to manage the voltage state of
the domain. This allows us to support a system-wide DVFS on Tegra.

Signed-off-by: Dmitry Osipenko <[email protected]>
---
drivers/soc/tegra/Kconfig | 6 ++
drivers/soc/tegra/Makefile | 1 +
drivers/soc/tegra/core-power-domain.c | 125 ++++++++++++++++++++++++++
include/soc/tegra/common.h | 6 ++
4 files changed, 138 insertions(+)
create mode 100644 drivers/soc/tegra/core-power-domain.c

diff --git a/drivers/soc/tegra/Kconfig b/drivers/soc/tegra/Kconfig
index bcd61ae59ba3..fccbc168dd87 100644
--- a/drivers/soc/tegra/Kconfig
+++ b/drivers/soc/tegra/Kconfig
@@ -16,6 +16,7 @@ config ARCH_TEGRA_2x_SOC
select SOC_TEGRA_COMMON
select SOC_TEGRA_FLOWCTRL
select SOC_TEGRA_PMC
+ select SOC_TEGRA_CORE_POWER_DOMAIN
select SOC_TEGRA20_VOLTAGE_COUPLER
select TEGRA_TIMER
help
@@ -31,6 +32,7 @@ config ARCH_TEGRA_3x_SOC
select SOC_TEGRA_COMMON
select SOC_TEGRA_FLOWCTRL
select SOC_TEGRA_PMC
+ select SOC_TEGRA_CORE_POWER_DOMAIN
select SOC_TEGRA30_VOLTAGE_COUPLER
select TEGRA_TIMER
help
@@ -170,3 +172,7 @@ config SOC_TEGRA20_VOLTAGE_COUPLER
config SOC_TEGRA30_VOLTAGE_COUPLER
bool "Voltage scaling support for Tegra30 SoCs"
depends on ARCH_TEGRA_3x_SOC || COMPILE_TEST
+
+config SOC_TEGRA_CORE_POWER_DOMAIN
+ bool
+ select PM_GENERIC_DOMAINS
diff --git a/drivers/soc/tegra/Makefile b/drivers/soc/tegra/Makefile
index 9c809c1814bd..8f1294f954b4 100644
--- a/drivers/soc/tegra/Makefile
+++ b/drivers/soc/tegra/Makefile
@@ -7,3 +7,4 @@ obj-$(CONFIG_SOC_TEGRA_PMC) += pmc.o
obj-$(CONFIG_SOC_TEGRA_POWERGATE_BPMP) += powergate-bpmp.o
obj-$(CONFIG_SOC_TEGRA20_VOLTAGE_COUPLER) += regulators-tegra20.o
obj-$(CONFIG_SOC_TEGRA30_VOLTAGE_COUPLER) += regulators-tegra30.o
+obj-$(CONFIG_SOC_TEGRA_CORE_POWER_DOMAIN) += core-power-domain.o
diff --git a/drivers/soc/tegra/core-power-domain.c b/drivers/soc/tegra/core-power-domain.c
new file mode 100644
index 000000000000..7c0cec8c79fd
--- /dev/null
+++ b/drivers/soc/tegra/core-power-domain.c
@@ -0,0 +1,125 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * NVIDIA Tegra SoC Core Power Domain Driver
+ */
+
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/pm_domain.h>
+#include <linux/pm_opp.h>
+#include <linux/slab.h>
+
+#include <soc/tegra/common.h>
+
+static struct lock_class_key tegra_core_domain_lock_class;
+static bool tegra_core_domain_state_synced;
+
+static int tegra_genpd_set_performance_state(struct generic_pm_domain *genpd,
+ unsigned int level)
+{
+ struct dev_pm_opp *opp;
+ int err;
+
+ opp = dev_pm_opp_find_level_ceil(&genpd->dev, &level);
+ if (IS_ERR(opp)) {
+ dev_err(&genpd->dev, "failed to find OPP for level %u: %pe\n",
+ level, opp);
+ return PTR_ERR(opp);
+ }
+
+ err = dev_pm_opp_set_voltage(&genpd->dev, opp);
+ dev_pm_opp_put(opp);
+
+ if (err) {
+ dev_err(&genpd->dev, "failed to set voltage to %duV: %d\n",
+ level, err);
+ return err;
+ }
+
+ return 0;
+}
+
+static unsigned int
+tegra_genpd_opp_to_performance_state(struct generic_pm_domain *genpd,
+ struct dev_pm_opp *opp)
+{
+ return dev_pm_opp_get_level(opp);
+}
+
+static int tegra_core_domain_probe(struct platform_device *pdev)
+{
+ struct generic_pm_domain *genpd;
+ struct opp_table *opp_table;
+ const char *rname = "power";
+ int err;
+
+ genpd = devm_kzalloc(&pdev->dev, sizeof(*genpd), GFP_KERNEL);
+ if (!genpd)
+ return -ENOMEM;
+
+ genpd->name = pdev->dev.of_node->name;
+ genpd->set_performance_state = tegra_genpd_set_performance_state;
+ genpd->opp_to_performance_state = tegra_genpd_opp_to_performance_state;
+
+ opp_table = devm_pm_opp_set_regulators(&pdev->dev, &rname, 1);
+ if (IS_ERR(opp_table))
+ return dev_err_probe(&pdev->dev, PTR_ERR(opp_table),
+ "failed to set OPP regulator\n");
+
+ err = pm_genpd_init(genpd, NULL, false);
+ if (err) {
+ dev_err(&pdev->dev, "failed to init genpd: %d\n", err);
+ return err;
+ }
+
+ /*
+ * We have a "PMC -> Core" hierarchy of the power domains where
+ * PMC needs to resume and change performance (voltage) of the
+ * Core domain from the PMC GENPD on/off callbacks, hence we need
+ * to annotate the lock in order to remove confusion from the
+ * lockdep checker when a nested access happens.
+ */
+ lockdep_set_class(&genpd->mlock, &tegra_core_domain_lock_class);
+
+ err = of_genpd_add_provider_simple(pdev->dev.of_node, genpd);
+ if (err) {
+ dev_err(&pdev->dev, "failed to add genpd: %d\n", err);
+ goto remove_genpd;
+ }
+
+ return 0;
+
+remove_genpd:
+ pm_genpd_remove(genpd);
+
+ return err;
+}
+
+bool tegra_soc_core_domain_state_synced(void)
+{
+ return tegra_core_domain_state_synced;
+}
+
+static void tegra_core_domain_sync_state(struct device *dev)
+{
+ tegra_core_domain_state_synced = true;
+
+ dev_pm_opp_sync_regulators(dev);
+}
+
+static const struct of_device_id tegra_core_domain_match[] = {
+ { .compatible = "nvidia,tegra20-core-domain", },
+ { .compatible = "nvidia,tegra30-core-domain", },
+ { }
+};
+
+static struct platform_driver tegra_core_domain_driver = {
+ .driver = {
+ .name = "tegra-core-power",
+ .of_match_table = tegra_core_domain_match,
+ .suppress_bind_attrs = true,
+ .sync_state = tegra_core_domain_sync_state,
+ },
+ .probe = tegra_core_domain_probe,
+};
+builtin_platform_driver(tegra_core_domain_driver);
diff --git a/include/soc/tegra/common.h b/include/soc/tegra/common.h
index 57b56793a9e5..6c2ccbbbf073 100644
--- a/include/soc/tegra/common.h
+++ b/include/soc/tegra/common.h
@@ -27,6 +27,7 @@ struct tegra_core_opp_params {

#ifdef CONFIG_ARCH_TEGRA
bool soc_is_tegra(void);
+bool tegra_soc_core_domain_state_synced(void);
int devm_tegra_core_dev_init_opp_table(struct device *dev,
struct tegra_core_opp_params *cfg);
#else
@@ -35,6 +36,11 @@ static inline bool soc_is_tegra(void)
return false;
}

+static inline bool tegra_soc_core_domain_state_synced(void)
+{
+ return false;
+}
+
static inline int
devm_tegra_core_dev_init_opp_table(struct device *dev,
struct tegra_core_opp_params *cfg)
--
2.29.2

2020-12-17 18:10:47

by Dmitry Osipenko

[permalink] [raw]
Subject: [PATCH v2 09/48] opp: Add dev_pm_opp_sync_regulators()

Extend OPP API with dev_pm_opp_sync_regulators() function, which syncs
voltage state of regulators.

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

diff --git a/drivers/opp/core.c b/drivers/opp/core.c
index 4268eb359915..500d6c716283 100644
--- a/drivers/opp/core.c
+++ b/drivers/opp/core.c
@@ -2499,3 +2499,45 @@ void dev_pm_opp_remove_table(struct device *dev)
dev_pm_opp_put_opp_table(opp_table);
}
EXPORT_SYMBOL_GPL(dev_pm_opp_remove_table);
+
+/**
+ * dev_pm_opp_sync_regulators() - Sync state of voltage regulators
+ * @dev: device for which we do this operation
+ *
+ * Sync voltage state of the OPP table regulators.
+ *
+ * Return: 0 on success or a negative error value.
+ */
+int dev_pm_opp_sync_regulators(struct device *dev)
+{
+ struct opp_table *opp_table;
+ struct regulator *reg;
+ int i, ret = 0;
+
+ /* Device may not have OPP table */
+ opp_table = _find_opp_table(dev);
+ if (IS_ERR(opp_table))
+ return 0;
+
+ /* Regulator may not be required for the device */
+ if (!opp_table->regulators)
+ goto put_table;
+
+ /* Nothing to sync if voltage wasn't changed */
+ if (!opp_table->enabled)
+ goto put_table;
+
+ for (i = 0; i < opp_table->regulator_count; i++) {
+ reg = opp_table->regulators[i];
+ ret = regulator_sync_voltage(reg);
+ if (ret)
+ break;
+ }
+
+put_table:
+ /* Drop reference taken by _find_opp_table() */
+ dev_pm_opp_put_opp_table(opp_table);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(dev_pm_opp_sync_regulators);
diff --git a/include/linux/pm_opp.h b/include/linux/pm_opp.h
index 1435c054016a..4c79faa2025e 100644
--- a/include/linux/pm_opp.h
+++ b/include/linux/pm_opp.h
@@ -156,6 +156,7 @@ int dev_pm_opp_set_sharing_cpus(struct device *cpu_dev, const struct cpumask *cp
int dev_pm_opp_get_sharing_cpus(struct device *cpu_dev, struct cpumask *cpumask);
void dev_pm_opp_remove_table(struct device *dev);
void dev_pm_opp_cpumask_remove_table(const struct cpumask *cpumask);
+int dev_pm_opp_sync_regulators(struct device *dev);
#else
static inline struct opp_table *dev_pm_opp_get_opp_table(struct device *dev)
{
@@ -366,6 +367,11 @@ static inline void dev_pm_opp_cpumask_remove_table(const struct cpumask *cpumask
{
}

+static inline int dev_pm_opp_sync_regulators(struct device *dev)
+{
+ return -ENOTSUPP;
+}
+
#endif /* CONFIG_PM_OPP */

#if defined(CONFIG_PM_OPP) && defined(CONFIG_OF)
@@ -442,6 +448,11 @@ static inline int dev_pm_opp_of_find_icc_paths(struct device *dev, struct opp_ta
{
return -ENOTSUPP;
}
+
+static inline int dev_pm_opp_sync_regulators(struct device *dev)
+{
+ return -ENOTSUPP;
+}
#endif

#endif /* __LINUX_OPP_H__ */
--
2.29.2

2020-12-17 18:10:57

by Dmitry Osipenko

[permalink] [raw]
Subject: [PATCH v2 16/48] opp: Handle missing OPP table in dev_pm_opp_xlate_performance_state()

NVIDIA Tegra SoCs have a power domains topology such that child domains
only clamp a power rail, while parent domain controls shared performance
state of the multiple child domains. In this case child's domain doesn't
need to have OPP table. Hence we want to allow children power domains to
pass performance state to the parent domain if child's domain doesn't have
OPP table.

The dev_pm_opp_xlate_performance_state() gets src_table=NULL if a child
power domain doesn't have OPP table and in this case we should pass the
performance state to the parent domain.

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

diff --git a/drivers/opp/core.c b/drivers/opp/core.c
index 625dae7a5ecb..34f7e530d941 100644
--- a/drivers/opp/core.c
+++ b/drivers/opp/core.c
@@ -2255,7 +2255,7 @@ int dev_pm_opp_xlate_performance_state(struct opp_table *src_table,
* and so none of them have the "required-opps" property set. Return the
* pstate of the src_table as it is in such cases.
*/
- if (!src_table->required_opp_count)
+ if (!src_table || !src_table->required_opp_count)
return pstate;

for (i = 0; i < src_table->required_opp_count; i++) {
--
2.29.2

2020-12-17 18:10:57

by Dmitry Osipenko

[permalink] [raw]
Subject: [PATCH v2 03/48] dt-bindings: memory: tegra124: emc: Replace core regulator with power domain

Power domain fits much better than a voltage regulator in regards to
a proper hardware description and from a software perspective as well.
Hence replace the core regulator with the power domain. Note that this
doesn't affect any existing DTBs because we haven't started to use the
regulator yet, and thus, it's okay to change it.

Signed-off-by: Dmitry Osipenko <[email protected]>
---
.../bindings/memory-controllers/nvidia,tegra124-emc.yaml | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/Documentation/devicetree/bindings/memory-controllers/nvidia,tegra124-emc.yaml b/Documentation/devicetree/bindings/memory-controllers/nvidia,tegra124-emc.yaml
index 09bde65e1955..2ab42b05e198 100644
--- a/Documentation/devicetree/bindings/memory-controllers/nvidia,tegra124-emc.yaml
+++ b/Documentation/devicetree/bindings/memory-controllers/nvidia,tegra124-emc.yaml
@@ -37,9 +37,9 @@ properties:
description:
phandle of the memory controller node

- core-supply:
+ power-domains:
description:
- Phandle of voltage regulator of the SoC "core" power domain.
+ Phandle to the SoC "core" power domain.

operating-points-v2:
description:
@@ -370,7 +370,7 @@ examples:

nvidia,memory-controller = <&mc>;
operating-points-v2 = <&dvfs_opp_table>;
- core-supply = <&vdd_core>;
+ power-domains = <&domain>;

#interconnect-cells = <0>;

--
2.29.2

2020-12-17 18:11:04

by Dmitry Osipenko

[permalink] [raw]
Subject: [PATCH v2 40/48] media: staging: tegra-vde: Support OPP and generic power domain

Add OPP and generic power domain support to the video decoder driver.
This allows us to utilize a modern GENPD API for newer device-trees and
support DVFS of the decoder hardware. Note that older DTBs will continue
to work like they did it before this patch.

Tested-by: Peter Geis <[email protected]>
Tested-by: Nicolas Chauvet <[email protected]>
Signed-off-by: Dmitry Osipenko <[email protected]>
---
drivers/staging/media/tegra-vde/vde.c | 63 ++++++++++++++++++++++-----
1 file changed, 53 insertions(+), 10 deletions(-)

diff --git a/drivers/staging/media/tegra-vde/vde.c b/drivers/staging/media/tegra-vde/vde.c
index 28845b5bafaf..5be26b9bd5d8 100644
--- a/drivers/staging/media/tegra-vde/vde.c
+++ b/drivers/staging/media/tegra-vde/vde.c
@@ -15,11 +15,14 @@
#include <linux/miscdevice.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 <linux/slab.h>
#include <linux/uaccess.h>

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

#include "uapi.h"
@@ -918,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);
+ dev_pm_opp_set_rate(dev, 0);

return 0;
}
@@ -934,18 +941,44 @@ 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 = dev_pm_opp_set_rate(dev, clk_get_rate(vde->clk));
+ if (err) {
+ dev_err(dev, "Failed to prepare OPP: %d\n", err);
+ return err;
+ }
+
+ err = reset_control_acquire(vde->rst);
if (err) {
- dev_err(dev, "Failed to power up HW : %d\n", err);
+ dev_err(dev, "Failed to acquire reset: %d\n", err);
return err;
}

+ 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);
+
+ return err;
}

static int tegra_vde_probe(struct platform_device *pdev)
{
+ struct tegra_core_opp_params opp_params = {};
struct device *dev = &pdev->dev;
struct tegra_vde *vde;
int irq, err;
@@ -999,7 +1032,7 @@ 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);
@@ -1024,6 +1057,12 @@ static int tegra_vde_probe(struct platform_device *pdev)
return err;
}

+ opp_params.init_state = true;
+
+ err = devm_tegra_core_dev_init_opp_table(dev, &opp_params);
+ if (err && err != -ENODEV)
+ return err;
+
vde->iram_pool = of_gen_pool_get(dev->of_node, "iram", 0);
if (!vde->iram_pool) {
dev_err(dev, "Could not get IRAM pool\n");
@@ -1117,8 +1156,12 @@ 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);
+ if (pm_runtime_status_suspended(&pdev->dev)) {
+ if (pdev->dev.pm_domain)
+ dev_pm_genpd_resume(&pdev->dev);
+ else
+ tegra_vde_runtime_resume(&pdev->dev);
+ }
}

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

2020-12-17 18:11:04

by Dmitry Osipenko

[permalink] [raw]
Subject: [PATCH v2 06/48] dt-bindings: clock: tegra: Document clocks sub-node

Document "clocks" sub-node which describes Tegra SoC clocks that require
a higher voltage of the core power domain in order to operate properly on
a higher rates.

Signed-off-by: Dmitry Osipenko <[email protected]>
---
.../bindings/clock/nvidia,tegra20-car.txt | 26 +++++++++++++++++++
.../bindings/clock/nvidia,tegra30-car.txt | 26 +++++++++++++++++++
2 files changed, 52 insertions(+)

diff --git a/Documentation/devicetree/bindings/clock/nvidia,tegra20-car.txt b/Documentation/devicetree/bindings/clock/nvidia,tegra20-car.txt
index 6c5901b503d0..353354477785 100644
--- a/Documentation/devicetree/bindings/clock/nvidia,tegra20-car.txt
+++ b/Documentation/devicetree/bindings/clock/nvidia,tegra20-car.txt
@@ -19,6 +19,16 @@ Required properties :
In clock consumers, this cell represents the bit number in the CAR's
array of CLK_RST_CONTROLLER_RST_DEVICES_* registers.

+Optional child sub-node "clocks" should contain nodes matching the clocks
+on the Tegra SoC. Refer to Tegra TRM for mode details on the clock nodes.
+
+Required properties :
+- compatible : Should be "nvidia,tegra20-clock".
+- operating-points-v2: See ../bindings/opp/opp.txt for details.
+- clocks : Should contain clock which corresponds to the node.
+- power-domains: Phandle to a power domain node as described by generic
+ PM domain bindings.
+
Example SoC include file:

/ {
@@ -27,6 +37,22 @@ Example SoC include file:
reg = <0x60006000 0x1000>;
#clock-cells = <1>;
#reset-cells = <1>;
+
+ clocks {
+ hdmi {
+ compatible = "nvidia,tegra20-clock";
+ operating-points-v2 = <&hdmi_opp_table>;
+ clocks = <&tegra_car TEGRA20_CLK_HDMI>;
+ power-domains = <&domain>;
+ };
+
+ pll_m {
+ compatible = "nvidia,tegra20-clock";
+ operating-points-v2 = <&pll_m_opp_table>;
+ clocks = <&tegra_car TEGRA20_CLK_PLL_M>;
+ power-domains = <&domain>;
+ };
+ };
};

usb@c5004000 {
diff --git a/Documentation/devicetree/bindings/clock/nvidia,tegra30-car.txt b/Documentation/devicetree/bindings/clock/nvidia,tegra30-car.txt
index 63618cde12df..bc7bbdaa9d3f 100644
--- a/Documentation/devicetree/bindings/clock/nvidia,tegra30-car.txt
+++ b/Documentation/devicetree/bindings/clock/nvidia,tegra30-car.txt
@@ -19,6 +19,16 @@ Required properties :
In clock consumers, this cell represents the bit number in the CAR's
array of CLK_RST_CONTROLLER_RST_DEVICES_* registers.

+Optional child sub-node "clocks" should contain nodes matching the clocks
+on the Tegra SoC. Refer to Tegra TRM for mode details on the clock nodes.
+
+Required properties :
+- compatible : Should be "nvidia,tegra30-clock".
+- operating-points-v2: See ../bindings/opp/opp.txt for details.
+- clocks : Should contain clock which corresponds to the node.
+- power-domains: Phandle to a power domain node as described by generic
+ PM domain bindings.
+
Example SoC include file:

/ {
@@ -31,6 +41,22 @@ Example SoC include file:

usb@c5004000 {
clocks = <&tegra_car TEGRA30_CLK_USB2>;
+
+ clocks {
+ hdmi {
+ compatible = "nvidia,tegra30-clock";
+ operating-points-v2 = <&hdmi_opp_table>;
+ clocks = <&tegra_car TEGRA30_CLK_HDMI>;
+ power-domains = <&domain>;
+ };
+
+ pll_m {
+ compatible = "nvidia,tegra30-clock";
+ operating-points-v2 = <&pll_m_opp_table>;
+ clocks = <&tegra_car TEGRA30_CLK_PLL_M>;
+ power-domains = <&domain>;
+ };
+ };
};
};

--
2.29.2

2020-12-17 18:11:06

by Dmitry Osipenko

[permalink] [raw]
Subject: [PATCH v2 08/48] regulator: Make regulator_sync_voltage() usable by coupled regulators

Make regulator_sync_voltage() to re-balance voltage state of a coupled
regulators instead of changing the voltage directly.

Signed-off-by: Dmitry Osipenko <[email protected]>
---
drivers/regulator/core.c | 6 ++++++
1 file changed, 6 insertions(+)

diff --git a/drivers/regulator/core.c b/drivers/regulator/core.c
index ca03d8e70bd1..576efb815eb8 100644
--- a/drivers/regulator/core.c
+++ b/drivers/regulator/core.c
@@ -4131,6 +4131,12 @@ int regulator_sync_voltage(struct regulator *regulator)
if (ret < 0)
goto out;

+ /* balance only if there are regulators coupled */
+ if (rdev->coupling_desc.n_coupled > 1) {
+ ret = regulator_balance_voltage(rdev, PM_SUSPEND_ON);
+ goto out;
+ }
+
ret = _regulator_do_set_voltage(rdev, min_uV, max_uV);

out:
--
2.29.2

2020-12-17 18:11:17

by Dmitry Osipenko

[permalink] [raw]
Subject: [PATCH v2 30/48] soc/tegra: regulators: Fix locking up when voltage-spread is out of range

Fix voltage coupler lockup which happens when voltage-spread is out
of range due to a bug in the code. The max-spread requirement shall be
accounted when CPU regulator doesn't have consumers. This problem is
observed on Tegra30 Ouya game console once system-wide DVFS is enabled
in a device-tree.

Fixes: 783807436f36 ("soc/tegra: regulators: Add regulators coupler for Tegra30")
Cc: [email protected]
Tested-by: Peter Geis <[email protected]>
Reported-by: Peter Geis <[email protected]>
Signed-off-by: Dmitry Osipenko <[email protected]>
---
drivers/soc/tegra/regulators-tegra30.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/soc/tegra/regulators-tegra30.c b/drivers/soc/tegra/regulators-tegra30.c
index 7f21f31de09d..0e776b20f625 100644
--- a/drivers/soc/tegra/regulators-tegra30.c
+++ b/drivers/soc/tegra/regulators-tegra30.c
@@ -178,7 +178,7 @@ static int tegra30_voltage_update(struct tegra_regulator_coupler *tegra,
* survive the voltage drop if it's running on a higher frequency.
*/
if (!cpu_min_uV_consumers)
- cpu_min_uV = cpu_uV;
+ cpu_min_uV = max(cpu_uV, cpu_min_uV);

/*
* Bootloader shall set up voltages correctly, but if it
--
2.29.2

2020-12-17 18:11:18

by Dmitry Osipenko

[permalink] [raw]
Subject: [PATCH v2 48/48] ARM: tegra: cardhu: Support CPU voltage scaling and thermal throttling

Enable CPU voltage scaling and thermal throttling on Tegra30 Cardhu board.

Signed-off-by: Dmitry Osipenko <[email protected]>
---
arch/arm/boot/dts/tegra30-cardhu.dtsi | 61 ++++++++++++++++++++++++++-
1 file changed, 60 insertions(+), 1 deletion(-)

diff --git a/arch/arm/boot/dts/tegra30-cardhu.dtsi b/arch/arm/boot/dts/tegra30-cardhu.dtsi
index d74c9ca78a7f..08c0ea4e6228 100644
--- a/arch/arm/boot/dts/tegra30-cardhu.dtsi
+++ b/arch/arm/boot/dts/tegra30-cardhu.dtsi
@@ -1,6 +1,9 @@
// SPDX-License-Identifier: GPL-2.0
#include <dt-bindings/input/input.h>
+#include <dt-bindings/thermal/thermal.h>
#include "tegra30.dtsi"
+#include "tegra30-cpu-opp.dtsi"
+#include "tegra30-cpu-opp-microvolt.dtsi"

/**
* This file contains common DT entry for all fab version of Cardhu.
@@ -339,12 +342,13 @@ ldo8_reg: ldo8 {
};
};

- temperature-sensor@4c {
+ nct1008: temperature-sensor@4c {
compatible = "onnn,nct1008";
reg = <0x4c>;
vcc-supply = <&sys_3v3_reg>;
interrupt-parent = <&gpio>;
interrupts = <TEGRA_GPIO(CC, 2) IRQ_TYPE_LEVEL_LOW>;
+ #thermal-sensor-cells = <1>;
};

vdd_core: tps62361@60 {
@@ -438,6 +442,29 @@ clk32k_in: clock@0 {
#clock-cells = <0>;
};

+ cpus {
+ cpu0: cpu@0 {
+ cpu-supply = <&vddctrl_reg>;
+ operating-points-v2 = <&cpu0_opp_table>;
+ #cooling-cells = <2>;
+ };
+
+ cpu@1 {
+ cpu-supply = <&vddctrl_reg>;
+ operating-points-v2 = <&cpu0_opp_table>;
+ };
+
+ cpu@2 {
+ cpu-supply = <&vddctrl_reg>;
+ operating-points-v2 = <&cpu0_opp_table>;
+ };
+
+ cpu@3 {
+ cpu-supply = <&vddctrl_reg>;
+ operating-points-v2 = <&cpu0_opp_table>;
+ };
+ };
+
panel: panel {
compatible = "chunghwa,claa101wb01";
ddc-i2c-bus = <&panelddc>;
@@ -617,6 +644,38 @@ sound {
<&tegra_car TEGRA30_CLK_EXTERN1>;
};

+ thermal-zones {
+ cpu-thermal {
+ polling-delay-passive = <1000>; /* milliseconds */
+ polling-delay = <5000>; /* milliseconds */
+
+ thermal-sensors = <&nct1008 1>;
+
+ trips {
+ trip0: cpu-alert0 {
+ /* throttle at 57C until temperature drops to 56.8C */
+ temperature = <57000>;
+ hysteresis = <200>;
+ type = "passive";
+ };
+
+ trip1: cpu-crit {
+ /* shut down at 60C */
+ temperature = <60000>;
+ hysteresis = <2000>;
+ type = "critical";
+ };
+ };
+
+ cooling-maps {
+ map0 {
+ trip = <&trip0>;
+ cooling-device = <&cpu0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>;
+ };
+ };
+ };
+ };
+
gpio-keys {
compatible = "gpio-keys";

--
2.29.2

2020-12-17 18:12:02

by Dmitry Osipenko

[permalink] [raw]
Subject: [PATCH v2 21/48] PM: domains: Add "performance" column to debug summary

Add "performance" column to debug summary which shows performance state
of all power domains and theirs devices.

Signed-off-by: Dmitry Osipenko <[email protected]>
---
drivers/base/power/domain.c | 22 +++++++++++++++++-----
1 file changed, 17 insertions(+), 5 deletions(-)

diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c
index a3e1bfc233d4..1c60dae8540c 100644
--- a/drivers/base/power/domain.c
+++ b/drivers/base/power/domain.c
@@ -2951,7 +2951,15 @@ static void rtpm_status_str(struct seq_file *s, struct device *dev)
else
WARN_ON(1);

- seq_puts(s, p);
+ seq_printf(s, "%-25s ", p);
+}
+
+static void perf_status_str(struct seq_file *s, struct device *dev)
+{
+ struct generic_pm_domain_data *gpd_data;
+
+ gpd_data = to_gpd_data(dev->power.subsys_data->domain_data);
+ seq_put_decimal_ull(s, "", gpd_data->performance_state);
}

static int genpd_summary_one(struct seq_file *s,
@@ -2979,7 +2987,7 @@ static int genpd_summary_one(struct seq_file *s,
else
snprintf(state, sizeof(state), "%s",
status_lookup[genpd->status]);
- seq_printf(s, "%-30s %-15s ", genpd->name, state);
+ seq_printf(s, "%-30s %-50s %u", genpd->name, state, genpd->performance_state);

/*
* Modifications on the list require holding locks on both
@@ -2987,7 +2995,10 @@ static int genpd_summary_one(struct seq_file *s,
* Also genpd->name is immutable.
*/
list_for_each_entry(link, &genpd->parent_links, parent_node) {
- seq_printf(s, "%s", link->child->name);
+ if (list_is_first(&link->parent_node, &genpd->parent_links))
+ seq_printf(s, "\n%50s ", link->child->name);
+ else
+ seq_printf(s, "%s", link->child->name);
if (!list_is_last(&link->parent_node, &genpd->parent_links))
seq_puts(s, ", ");
}
@@ -3001,6 +3012,7 @@ static int genpd_summary_one(struct seq_file *s,

seq_printf(s, "\n %-50s ", kobj_path);
rtpm_status_str(s, pm_data->dev);
+ perf_status_str(s, pm_data->dev);
kfree(kobj_path);
}

@@ -3016,9 +3028,9 @@ static int summary_show(struct seq_file *s, void *data)
struct generic_pm_domain *genpd;
int ret = 0;

- seq_puts(s, "domain status children\n");
+ seq_puts(s, "domain status children performance\n");
seq_puts(s, " /device runtime status\n");
- seq_puts(s, "----------------------------------------------------------------------\n");
+ seq_puts(s, "----------------------------------------------------------------------------------------------\n");

ret = mutex_lock_interruptible(&gpd_list_lock);
if (ret)
--
2.29.2

2020-12-17 18:12:08

by Dmitry Osipenko

[permalink] [raw]
Subject: [PATCH v2 31/48] soc/tegra: regulators: Support Core domain state syncing

The core voltage shall not drop until state of Core domain is synced,
i.e. all device drivers that use Core domain are loaded and ready.

Support Core domain state syncing. The Core domain driver invokes the
core-regulator voltage syncing once the state of domain is synced, at
this point the Core voltage is allowed to go lower.

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

diff --git a/drivers/soc/tegra/regulators-tegra20.c b/drivers/soc/tegra/regulators-tegra20.c
index 367a71a3cd10..e2c11d442591 100644
--- a/drivers/soc/tegra/regulators-tegra20.c
+++ b/drivers/soc/tegra/regulators-tegra20.c
@@ -16,6 +16,8 @@
#include <linux/regulator/driver.h>
#include <linux/regulator/machine.h>

+#include <soc/tegra/common.h>
+
struct tegra_regulator_coupler {
struct regulator_coupler coupler;
struct regulator_dev *core_rdev;
@@ -38,6 +40,21 @@ static int tegra20_core_limit(struct tegra_regulator_coupler *tegra,
int core_cur_uV;
int err;

+ /*
+ * Tegra20 SoC has critical DVFS-capable devices that are
+ * permanently-active or active at a boot time, like EMC
+ * (DRAM controller) or Display controller for example.
+ *
+ * The voltage of a CORE SoC power domain shall not be dropped below
+ * a minimum level, which is determined by device's clock rate.
+ * This means that we can't fully allow CORE voltage scaling until
+ * the state of all DVFS-critical CORE devices is synced.
+ */
+ if (tegra_soc_core_domain_state_synced()) {
+ pr_info_once("voltage state synced\n");
+ return 0;
+ }
+
if (tegra->core_min_uV > 0)
return tegra->core_min_uV;

@@ -58,7 +75,7 @@ static int tegra20_core_limit(struct tegra_regulator_coupler *tegra,
*/
tegra->core_min_uV = core_max_uV;

- pr_info("core minimum voltage limited to %duV\n", tegra->core_min_uV);
+ pr_info("core voltage initialized to %duV\n", tegra->core_min_uV);

return tegra->core_min_uV;
}
diff --git a/drivers/soc/tegra/regulators-tegra30.c b/drivers/soc/tegra/regulators-tegra30.c
index 0e776b20f625..42d675b79fa3 100644
--- a/drivers/soc/tegra/regulators-tegra30.c
+++ b/drivers/soc/tegra/regulators-tegra30.c
@@ -16,6 +16,7 @@
#include <linux/regulator/driver.h>
#include <linux/regulator/machine.h>

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

struct tegra_regulator_coupler {
@@ -39,6 +40,21 @@ static int tegra30_core_limit(struct tegra_regulator_coupler *tegra,
int core_cur_uV;
int err;

+ /*
+ * Tegra30 SoC has critical DVFS-capable devices that are
+ * permanently-active or active at a boot time, like EMC
+ * (DRAM controller) or Display controller for example.
+ *
+ * The voltage of a CORE SoC power domain shall not be dropped below
+ * a minimum level, which is determined by device's clock rate.
+ * This means that we can't fully allow CORE voltage scaling until
+ * the state of all DVFS-critical CORE devices is synced.
+ */
+ if (tegra_soc_core_domain_state_synced()) {
+ pr_info_once("voltage state synced\n");
+ return 0;
+ }
+
if (tegra->core_min_uV > 0)
return tegra->core_min_uV;

@@ -59,7 +75,7 @@ static int tegra30_core_limit(struct tegra_regulator_coupler *tegra,
*/
tegra->core_min_uV = core_max_uV;

- pr_info("core minimum voltage limited to %duV\n", tegra->core_min_uV);
+ pr_info("core voltage initialized to %duV\n", tegra->core_min_uV);

return tegra->core_min_uV;
}
--
2.29.2

2020-12-17 18:12:16

by Dmitry Osipenko

[permalink] [raw]
Subject: [PATCH v2 23/48] soc/tegra: pmc: Pulse resets after removing power clamp

The GR3D1 hardware unit needs to pulse hardware reset after removing power
clamp, otherwise reset won't be deasserted. Hence give reset a pulse after
removing the clamp. This stayed unnoticed previously because power
management wasn't supported by the 3D driver until recently and all power
gates are usually ungated after bootloader by default.

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

diff --git a/drivers/soc/tegra/pmc.c b/drivers/soc/tegra/pmc.c
index fd2ba3c59178..985373ce52b1 100644
--- a/drivers/soc/tegra/pmc.c
+++ b/drivers/soc/tegra/pmc.c
@@ -654,6 +654,14 @@ static int tegra_powergate_power_up(struct tegra_powergate *pg,

usleep_range(10, 20);

+ /*
+ * Some hardware blocks may need a 0->1->0 reset pulse in order
+ * to propagate the reset, Tegra30 3D1 is one example.
+ */
+ err = reset_control_reset(pg->reset);
+ if (err)
+ goto powergate_off;
+
if (pg->pmc->soc->needs_mbist_war)
err = tegra210_clk_handle_mbist_war(pg->id);
if (err)
--
2.29.2

2020-12-17 18:12:21

by Dmitry Osipenko

[permalink] [raw]
Subject: [PATCH v2 46/48] ARM: tegra: ventana: Enable core voltage scaling

Allow lower core voltages on Ventana board.

Signed-off-by: Dmitry Osipenko <[email protected]>
---
arch/arm/boot/dts/tegra20-ventana.dts | 32 ++++++++++++++++++++-------
1 file changed, 24 insertions(+), 8 deletions(-)

diff --git a/arch/arm/boot/dts/tegra20-ventana.dts b/arch/arm/boot/dts/tegra20-ventana.dts
index 0b03b3b0fd0c..14ace2ef749c 100644
--- a/arch/arm/boot/dts/tegra20-ventana.dts
+++ b/arch/arm/boot/dts/tegra20-ventana.dts
@@ -422,16 +422,26 @@ sys_reg: sys {

vdd_core: sm0 {
regulator-name = "vdd_sm0,vdd_core";
- regulator-min-microvolt = <1200000>;
- regulator-max-microvolt = <1200000>;
+ regulator-min-microvolt = <950000>;
+ regulator-max-microvolt = <1300000>;
+ regulator-coupled-with = <&rtc_vdd &vdd_cpu>;
+ regulator-coupled-max-spread = <170000 550000>;
regulator-always-on;
+ regulator-boot-on;
+
+ nvidia,tegra-core-regulator;
};

- sm1 {
+ vdd_cpu: sm1 {
regulator-name = "vdd_sm1,vdd_cpu";
- regulator-min-microvolt = <1000000>;
- regulator-max-microvolt = <1000000>;
+ regulator-min-microvolt = <750000>;
+ regulator-max-microvolt = <1125000>;
+ regulator-coupled-with = <&vdd_core &rtc_vdd>;
+ regulator-coupled-max-spread = <550000 550000>;
regulator-always-on;
+ regulator-boot-on;
+
+ nvidia,tegra-cpu-regulator;
};

sm2_reg: sm2 {
@@ -450,10 +460,16 @@ ldo1 {
regulator-always-on;
};

- ldo2 {
+ rtc_vdd: ldo2 {
regulator-name = "vdd_ldo2,vdd_rtc";
- regulator-min-microvolt = <1200000>;
- regulator-max-microvolt = <1200000>;
+ regulator-min-microvolt = <950000>;
+ regulator-max-microvolt = <1300000>;
+ regulator-coupled-with = <&vdd_core &vdd_cpu>;
+ regulator-coupled-max-spread = <170000 550000>;
+ regulator-always-on;
+ regulator-boot-on;
+
+ nvidia,tegra-rtc-regulator;
};

ldo3 {
--
2.29.2

2020-12-17 18:12:23

by Dmitry Osipenko

[permalink] [raw]
Subject: [PATCH v2 27/48] soc/tegra: Add CONFIG_SOC_TEGRA_COMMON and select PM_OPP by default

Add new Kconfig SOC_TEGRA_COMMON option which selects configuration
options that are common for all Tegra SoCs. Select PM_OPP by default
since from now on OPPs will be used by Tegra drivers which present on
all SoC generations, like display controller driver for example.

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

diff --git a/drivers/soc/tegra/Kconfig b/drivers/soc/tegra/Kconfig
index 976dee036470..bcd61ae59ba3 100644
--- a/drivers/soc/tegra/Kconfig
+++ b/drivers/soc/tegra/Kconfig
@@ -13,6 +13,7 @@ config ARCH_TEGRA_2x_SOC
select PINCTRL_TEGRA20
select PL310_ERRATA_727915 if CACHE_L2X0
select PL310_ERRATA_769419 if CACHE_L2X0
+ select SOC_TEGRA_COMMON
select SOC_TEGRA_FLOWCTRL
select SOC_TEGRA_PMC
select SOC_TEGRA20_VOLTAGE_COUPLER
@@ -27,6 +28,7 @@ config ARCH_TEGRA_3x_SOC
select ARM_ERRATA_764369 if SMP
select PINCTRL_TEGRA30
select PL310_ERRATA_769419 if CACHE_L2X0
+ select SOC_TEGRA_COMMON
select SOC_TEGRA_FLOWCTRL
select SOC_TEGRA_PMC
select SOC_TEGRA30_VOLTAGE_COUPLER
@@ -40,6 +42,7 @@ config ARCH_TEGRA_114_SOC
select ARM_ERRATA_798181 if SMP
select HAVE_ARM_ARCH_TIMER
select PINCTRL_TEGRA114
+ select SOC_TEGRA_COMMON
select SOC_TEGRA_FLOWCTRL
select SOC_TEGRA_PMC
select TEGRA_TIMER
@@ -51,6 +54,7 @@ config ARCH_TEGRA_124_SOC
bool "Enable support for Tegra124 family"
select HAVE_ARM_ARCH_TIMER
select PINCTRL_TEGRA124
+ select SOC_TEGRA_COMMON
select SOC_TEGRA_FLOWCTRL
select SOC_TEGRA_PMC
select TEGRA_TIMER
@@ -66,6 +70,7 @@ if ARM64
config ARCH_TEGRA_132_SOC
bool "NVIDIA Tegra132 SoC"
select PINCTRL_TEGRA124
+ select SOC_TEGRA_COMMON
select SOC_TEGRA_FLOWCTRL
select SOC_TEGRA_PMC
help
@@ -77,6 +82,7 @@ config ARCH_TEGRA_132_SOC
config ARCH_TEGRA_210_SOC
bool "NVIDIA Tegra210 SoC"
select PINCTRL_TEGRA210
+ select SOC_TEGRA_COMMON
select SOC_TEGRA_FLOWCTRL
select SOC_TEGRA_PMC
select TEGRA_TIMER
@@ -99,6 +105,7 @@ config ARCH_TEGRA_186_SOC
select TEGRA_BPMP
select TEGRA_HSP_MBOX
select TEGRA_IVC
+ select SOC_TEGRA_COMMON
select SOC_TEGRA_PMC
help
Enable support for the NVIDIA Tegar186 SoC. The Tegra186 features a
@@ -115,6 +122,7 @@ config ARCH_TEGRA_194_SOC
select TEGRA_BPMP
select TEGRA_HSP_MBOX
select TEGRA_IVC
+ select SOC_TEGRA_COMMON
select SOC_TEGRA_PMC
help
Enable support for the NVIDIA Tegra194 SoC.
@@ -125,6 +133,7 @@ config ARCH_TEGRA_234_SOC
select TEGRA_BPMP
select TEGRA_HSP_MBOX
select TEGRA_IVC
+ select SOC_TEGRA_COMMON
select SOC_TEGRA_PMC
help
Enable support for the NVIDIA Tegra234 SoC.
@@ -132,6 +141,10 @@ config ARCH_TEGRA_234_SOC
endif
endif

+config SOC_TEGRA_COMMON
+ bool
+ select PM_OPP
+
config SOC_TEGRA_FUSE
def_bool y
depends on ARCH_TEGRA
--
2.29.2

2020-12-17 18:12:25

by Dmitry Osipenko

[permalink] [raw]
Subject: [PATCH v2 26/48] soc/tegra: Add devm_tegra_core_dev_init_opp_table()

Add common helper which initializes OPP table for Tegra SoC core devices.

Signed-off-by: Dmitry Osipenko <[email protected]>
---
drivers/soc/tegra/common.c | 137 +++++++++++++++++++++++++++++++++++++
include/soc/tegra/common.h | 35 ++++++++++
2 files changed, 172 insertions(+)

diff --git a/drivers/soc/tegra/common.c b/drivers/soc/tegra/common.c
index 3dc54f59cafe..1339d46f494d 100644
--- a/drivers/soc/tegra/common.c
+++ b/drivers/soc/tegra/common.c
@@ -3,9 +3,16 @@
* Copyright (C) 2014 NVIDIA CORPORATION. All rights reserved.
*/

+#define dev_fmt(fmt) "tegra-soc: " fmt
+
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/export.h>
#include <linux/of.h>
+#include <linux/pm_opp.h>

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

static const struct of_device_id tegra_machine_match[] = {
{ .compatible = "nvidia,tegra20", },
@@ -31,3 +38,133 @@ bool soc_is_tegra(void)

return match != NULL;
}
+
+static int tegra_core_dev_init_opp_state(struct device *dev)
+{
+ struct dev_pm_opp *opp;
+ unsigned long rate;
+ struct clk *clk;
+ int err;
+
+ clk = devm_clk_get(dev, NULL);
+ if (IS_ERR(clk)) {
+ dev_err(dev, "failed to get clk: %pe\n", clk);
+ return PTR_ERR(clk);
+ }
+
+ /*
+ * If voltage regulator presents, then we could select the fastest
+ * clock rate, but driver doesn't support power management and
+ * frequency scaling yet, hence the top freq OPP will vote for a
+ * very high voltage that will produce lot's of heat. Let's select
+ * OPP for the current/default rate for now.
+ *
+ * Clock rate should be pre-initialized (i.e. it's non-zero) either
+ * by clock driver or by assigned clocks in a device-tree.
+ */
+ rate = clk_get_rate(clk);
+ if (!rate) {
+ dev_err(dev, "failed to get clk rate\n");
+ return -EINVAL;
+ }
+
+ /* find suitable OPP for the clock rate and supportable by hardware */
+ opp = dev_pm_opp_find_freq_ceil(dev, &rate);
+
+ /*
+ * dev_pm_opp_set_rate() doesn't search for a floor clock rate and it
+ * will error out if default clock rate is too high, i.e. unsupported
+ * by a SoC hardware version. Hence will find floor rate by ourselves.
+ */
+ if (opp == ERR_PTR(-ERANGE))
+ opp = dev_pm_opp_find_freq_floor(dev, &rate);
+
+ err = PTR_ERR_OR_ZERO(opp);
+ if (err) {
+ dev_err(dev, "failed to get OPP for %ld Hz: %d\n",
+ rate, err);
+ return err;
+ }
+
+ dev_pm_opp_put(opp);
+
+ /*
+ * First dummy rate-set initializes voltage vote by setting voltage
+ * in accordance to the clock rate. We need to do this because some
+ * drivers currently don't support power management and clock is
+ * permanently enabled.
+ */
+ err = dev_pm_opp_set_rate(dev, rate);
+ if (err) {
+ dev_err(dev, "failed to initialize OPP clock: %d\n", err);
+ return err;
+ }
+
+ return 0;
+}
+
+/**
+ * devm_tegra_core_dev_init_opp_table() - initialize OPP table
+ * @cfg: pointer to the OPP table configuration
+ *
+ * This function will initialize OPP table and sync OPP state of a Tegra SoC
+ * core device.
+ *
+ * Return: 0 on success or errorno.
+ */
+int devm_tegra_core_dev_init_opp_table(struct device *dev,
+ struct tegra_core_opp_params *params)
+{
+ struct opp_table *opp_table;
+ u32 hw_version;
+ int err;
+
+ opp_table = devm_pm_opp_set_clkname(dev, NULL);
+ if (IS_ERR(opp_table)) {
+ dev_err(dev, "failed to set OPP clk %pe\n", opp_table);
+ return PTR_ERR(opp_table);
+ }
+
+ /* Tegra114+ don't support OPP yet */
+ if (!of_machine_is_compatible("nvidia,tegra20") &&
+ !of_machine_is_compatible("nvidia,tegra30"))
+ return -ENODEV;
+
+ if (of_machine_is_compatible("nvidia,tegra20"))
+ hw_version = BIT(tegra_sku_info.soc_process_id);
+ else
+ hw_version = BIT(tegra_sku_info.soc_speedo_id);
+
+ opp_table = devm_pm_opp_set_supported_hw(dev, &hw_version, 1);
+ if (IS_ERR(opp_table)) {
+ dev_err(dev, "failed to set OPP supported HW: %pe\n", opp_table);
+ return PTR_ERR(opp_table);
+ }
+
+ /*
+ * Older device-trees have an empty OPP table, hence we will get
+ * -ENODEV from devm_pm_opp_of_add_table() for the older DTBs.
+ *
+ * The OPP table presence also varies per-device and depending
+ * on a SoC generation, hence -ENODEV is expected to happen for
+ * the newer DTs as well.
+ */
+ err = devm_pm_opp_of_add_table(dev);
+ if (err) {
+ if (err == -ENODEV)
+ dev_err_once(dev, "OPP table not found, please update device-tree\n");
+ else
+ dev_err(dev, "failed to add OPP table: %d\n", err);
+
+ return err;
+ }
+
+ if (params->init_state) {
+ err = tegra_core_dev_init_opp_state(dev);
+ if (err)
+ return err;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(devm_tegra_core_dev_init_opp_table);
diff --git a/include/soc/tegra/common.h b/include/soc/tegra/common.h
index 98027a76ce3d..57b56793a9e5 100644
--- a/include/soc/tegra/common.h
+++ b/include/soc/tegra/common.h
@@ -6,6 +6,41 @@
#ifndef __SOC_TEGRA_COMMON_H__
#define __SOC_TEGRA_COMMON_H__

+#include <linux/errno.h>
+#include <linux/types.h>
+
+struct clk;
+struct device;
+
+/**
+ * Tegra SoC core device OPP table configuration
+ *
+ * @dev: pointer to the core device
+ * @clkname: name of clock used for DVFS
+ * @init_state: pre-initialize OPP state of a device
+ */
+struct tegra_core_opp_params {
+ struct device *dev;
+ const char *clkname;
+ bool init_state;
+};
+
+#ifdef CONFIG_ARCH_TEGRA
bool soc_is_tegra(void);
+int devm_tegra_core_dev_init_opp_table(struct device *dev,
+ struct tegra_core_opp_params *cfg);
+#else
+static inline bool soc_is_tegra(void)
+{
+ return false;
+}
+
+static inline int
+devm_tegra_core_dev_init_opp_table(struct device *dev,
+ struct tegra_core_opp_params *cfg)
+{
+ return -ENODEV;
+}
+#endif

#endif /* __SOC_TEGRA_COMMON_H__ */
--
2.29.2

2020-12-17 18:12:25

by Dmitry Osipenko

[permalink] [raw]
Subject: [PATCH v2 47/48] ARM: tegra: ventana: Support CPU voltage scaling and thermal throttling

Enable CPU voltage scaling and thermal throttling on Tegra20 Ventana board.

Signed-off-by: Dmitry Osipenko <[email protected]>
---
arch/arm/boot/dts/tegra20-ventana.dts | 40 ++++++++++++++++++++++++++-
1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/arch/arm/boot/dts/tegra20-ventana.dts b/arch/arm/boot/dts/tegra20-ventana.dts
index 14ace2ef749c..c2d9f38960bc 100644
--- a/arch/arm/boot/dts/tegra20-ventana.dts
+++ b/arch/arm/boot/dts/tegra20-ventana.dts
@@ -2,8 +2,10 @@
/dts-v1/;

#include <dt-bindings/input/input.h>
+#include <dt-bindings/thermal/thermal.h>
#include "tegra20.dtsi"
#include "tegra20-cpu-opp.dtsi"
+#include "tegra20-cpu-opp-microvolt.dtsi"

/ {
model = "NVIDIA Tegra20 Ventana evaluation board";
@@ -527,9 +529,10 @@ ldo_rtc {
};
};

- temperature-sensor@4c {
+ nct1008: temperature-sensor@4c {
compatible = "onnn,nct1008";
reg = <0x4c>;
+ #thermal-sensor-cells = <1>;
};
};

@@ -615,10 +618,13 @@ clk32k_in: clock@0 {

cpus {
cpu0: cpu@0 {
+ cpu-supply = <&vdd_cpu>;
operating-points-v2 = <&cpu0_opp_table>;
+ #cooling-cells = <2>;
};

cpu@1 {
+ cpu-supply = <&vdd_cpu>;
operating-points-v2 = <&cpu0_opp_table>;
};
};
@@ -717,4 +723,36 @@ sound {
<&tegra_car TEGRA20_CLK_CDEV1>;
clock-names = "pll_a", "pll_a_out0", "mclk";
};
+
+ thermal-zones {
+ cpu-thermal {
+ polling-delay-passive = <1000>; /* milliseconds */
+ polling-delay = <5000>; /* milliseconds */
+
+ thermal-sensors = <&nct1008 1>;
+
+ trips {
+ trip0: cpu-alert0 {
+ /* start throttling at 50C */
+ temperature = <50000>;
+ hysteresis = <200>;
+ type = "passive";
+ };
+
+ trip1: cpu-crit {
+ /* shut down at 60C */
+ temperature = <60000>;
+ hysteresis = <2000>;
+ type = "critical";
+ };
+ };
+
+ cooling-maps {
+ map0 {
+ trip = <&trip0>;
+ cooling-device = <&cpu0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>;
+ };
+ };
+ };
+ };
};
--
2.29.2

2020-12-17 18:12:26

by Dmitry Osipenko

[permalink] [raw]
Subject: [PATCH v2 45/48] ARM: tegra: acer-a500: Enable core voltage scaling

Allow lower core voltages on Acer A500.

Signed-off-by: Dmitry Osipenko <[email protected]>
---
arch/arm/boot/dts/tegra20-acer-a500-picasso.dts | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/arch/arm/boot/dts/tegra20-acer-a500-picasso.dts b/arch/arm/boot/dts/tegra20-acer-a500-picasso.dts
index 78b307370a46..6b851cab0efa 100644
--- a/arch/arm/boot/dts/tegra20-acer-a500-picasso.dts
+++ b/arch/arm/boot/dts/tegra20-acer-a500-picasso.dts
@@ -578,7 +578,7 @@ sys_reg: sys {

vdd_core: sm0 {
regulator-name = "vdd_sm0,vdd_core";
- regulator-min-microvolt = <1200000>;
+ regulator-min-microvolt = <950000>;
regulator-max-microvolt = <1300000>;
regulator-coupled-with = <&rtc_vdd &vdd_cpu>;
regulator-coupled-max-spread = <170000 550000>;
@@ -619,7 +619,7 @@ ldo1 {

rtc_vdd: ldo2 {
regulator-name = "vdd_ldo2,vdd_rtc";
- regulator-min-microvolt = <1200000>;
+ regulator-min-microvolt = <950000>;
regulator-max-microvolt = <1300000>;
regulator-coupled-with = <&vdd_core &vdd_cpu>;
regulator-coupled-max-spread = <170000 550000>;
--
2.29.2

2020-12-17 18:12:36

by Dmitry Osipenko

[permalink] [raw]
Subject: [PATCH v2 17/48] opp: Correct debug message in _opp_add_static_v2()

The debug message always prints rate=0 instead of a proper value, fix it.

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

diff --git a/drivers/opp/of.c b/drivers/opp/of.c
index 3b5a4c8bc62f..1f2038a4420b 100644
--- a/drivers/opp/of.c
+++ b/drivers/opp/of.c
@@ -755,7 +755,6 @@ static struct dev_pm_opp *_opp_add_static_v2(struct opp_table *opp_table,
struct device *dev, struct device_node *np)
{
struct dev_pm_opp *new_opp;
- u64 rate = 0;
u32 val;
int ret;
bool rate_not_available = false;
@@ -772,7 +771,8 @@ static struct dev_pm_opp *_opp_add_static_v2(struct opp_table *opp_table,

/* Check if the OPP supports hardware's hierarchy of versions or not */
if (!_opp_is_supported(dev, opp_table, np)) {
- dev_dbg(dev, "OPP not supported by hardware: %llu\n", rate);
+ dev_dbg(dev, "OPP not supported by hardware: %lu\n",
+ new_opp->rate);
goto free_opp;
}

--
2.29.2

2020-12-17 18:12:36

by Dmitry Osipenko

[permalink] [raw]
Subject: [PATCH v2 18/48] opp: Print OPP level in debug message of _opp_add_static_v2()

Print OPP level in debug message of _opp_add_static_v2(). This helps to
chase GENPD bugs.

Signed-off-by: Dmitry Osipenko <[email protected]>
---
drivers/opp/of.c | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/drivers/opp/of.c b/drivers/opp/of.c
index 1f2038a4420b..56b153ea5c56 100644
--- a/drivers/opp/of.c
+++ b/drivers/opp/of.c
@@ -822,10 +822,11 @@ static struct dev_pm_opp *_opp_add_static_v2(struct opp_table *opp_table,
if (new_opp->clock_latency_ns > opp_table->clock_latency_ns_max)
opp_table->clock_latency_ns_max = new_opp->clock_latency_ns;

- pr_debug("%s: turbo:%d rate:%lu uv:%lu uvmin:%lu uvmax:%lu latency:%lu\n",
+ pr_debug("%s: turbo:%d rate:%lu uv:%lu uvmin:%lu uvmax:%lu latency:%lu level:%u\n",
__func__, new_opp->turbo, new_opp->rate,
new_opp->supplies[0].u_volt, new_opp->supplies[0].u_volt_min,
- new_opp->supplies[0].u_volt_max, new_opp->clock_latency_ns);
+ new_opp->supplies[0].u_volt_max, new_opp->clock_latency_ns,
+ new_opp->level);

/*
* Notify the changes in the availability of the operable
--
2.29.2

2020-12-17 18:12:41

by Dmitry Osipenko

[permalink] [raw]
Subject: [PATCH v2 43/48] ARM: tegra: Add OPP tables and power domains to Tegra20 device-tree

Add OPP tables and power domains to the Tegra20 device-tree.

Signed-off-by: Dmitry Osipenko <[email protected]>
---
.../boot/dts/tegra20-acer-a500-picasso.dts | 4 +
arch/arm/boot/dts/tegra20-colibri.dtsi | 6 +-
arch/arm/boot/dts/tegra20-harmony.dts | 6 +-
arch/arm/boot/dts/tegra20-paz00.dts | 46 +-
.../arm/boot/dts/tegra20-peripherals-opp.dtsi | 941 ++++++++++++++++++
arch/arm/boot/dts/tegra20-seaboard.dts | 6 +-
arch/arm/boot/dts/tegra20-tamonten.dtsi | 6 +-
arch/arm/boot/dts/tegra20-trimslice.dts | 12 +
arch/arm/boot/dts/tegra20-ventana.dts | 6 +-
arch/arm/boot/dts/tegra20.dtsi | 220 ++++
10 files changed, 1241 insertions(+), 12 deletions(-)

diff --git a/arch/arm/boot/dts/tegra20-acer-a500-picasso.dts b/arch/arm/boot/dts/tegra20-acer-a500-picasso.dts
index 40c1bab22155..78b307370a46 100644
--- a/arch/arm/boot/dts/tegra20-acer-a500-picasso.dts
+++ b/arch/arm/boot/dts/tegra20-acer-a500-picasso.dts
@@ -834,6 +834,10 @@ rtc_32k_wifi: clock@1 {
clock-output-names = "kk3270032";
};

+ core-domain {
+ power-supply = <&vdd_core>;
+ };
+
cpus {
cpu0: cpu@0 {
cpu-supply = <&vdd_cpu>;
diff --git a/arch/arm/boot/dts/tegra20-colibri.dtsi b/arch/arm/boot/dts/tegra20-colibri.dtsi
index 585a5b441cf6..5ad803fea48f 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>;
@@ -701,6 +701,10 @@ usb-phy@c5004000 {
vbus-supply = <&reg_lan_v_bus>;
};

+ core-domain {
+ power-supply = <&vdd_core>;
+ };
+
clk32k_in: xtal3 {
compatible = "fixed-clock";
#clock-cells = <0>;
diff --git a/arch/arm/boot/dts/tegra20-harmony.dts b/arch/arm/boot/dts/tegra20-harmony.dts
index 86494cb4d5a1..762e12b73e7e 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>;
@@ -640,6 +640,10 @@ backlight: backlight {
default-brightness-level = <6>;
};

+ core-domain {
+ power-supply = <&vdd_core>;
+ };
+
clk32k_in: clock@0 {
compatible = "fixed-clock";
clock-frequency = <32768>;
diff --git a/arch/arm/boot/dts/tegra20-paz00.dts b/arch/arm/boot/dts/tegra20-paz00.dts
index 7e49112cd9a1..d30169722282 100644
--- a/arch/arm/boot/dts/tegra20-paz00.dts
+++ b/arch/arm/boot/dts/tegra20-paz00.dts
@@ -26,7 +26,19 @@ memory@0 {
};

host1x@50000000 {
+ core-supply = <&core_vdd_reg>;
+
+ gr2d@54140000 {
+ core-supply = <&core_vdd_reg>;
+ };
+
+ gr3d@54180000 {
+ core-supply = <&core_vdd_reg>;
+ };
+
dc@54200000 {
+ core-supply = <&core_vdd_reg>;
+
rgb {
status = "okay";

@@ -34,11 +46,16 @@ rgb {
};
};

+ dc@54240000 {
+ core-supply = <&core_vdd_reg>;
+ };
+
hdmi@54280000 {
status = "okay";

vdd-supply = <&hdmi_vdd_reg>;
pll-supply = <&hdmi_pll_reg>;
+ core-supply = <&core_vdd_reg>;

nvidia,ddc-i2c-bus = <&hdmi_ddc>;
nvidia,hpd-gpio = <&gpio TEGRA_GPIO(N, 7)
@@ -46,6 +63,10 @@ hdmi@54280000 {
};
};

+ vde@6001a000 {
+ core-supply = <&core_vdd_reg>;
+ };
+
pinmux@70000014 {
pinctrl-names = "default";
pinctrl-0 = <&state_default>;
@@ -314,6 +335,8 @@ nvec@7000c500 {
memory-controller@7000f400 {
nvidia,use-ram-code;

+ core-supply = <&core_vdd_reg>;
+
emc-tables@0 {
nvidia,ram-code = <0x0>;
#address-cells = <1>;
@@ -387,10 +410,10 @@ sys_reg: sys {

core_vdd_reg: sm0 {
regulator-name = "+1.2vs_sm0,vdd_core";
- regulator-min-microvolt = <1200000>;
- regulator-max-microvolt = <1225000>;
+ regulator-min-microvolt = <950000>;
+ regulator-max-microvolt = <1300000>;
regulator-coupled-with = <&rtc_vdd_reg &cpu_vdd_reg>;
- regulator-coupled-max-spread = <170000 450000>;
+ regulator-coupled-max-spread = <170000 550000>;
regulator-always-on;

nvidia,tegra-core-regulator;
@@ -401,7 +424,7 @@ cpu_vdd_reg: sm1 {
regulator-min-microvolt = <750000>;
regulator-max-microvolt = <1100000>;
regulator-coupled-with = <&core_vdd_reg &rtc_vdd_reg>;
- regulator-coupled-max-spread = <450000 450000>;
+ regulator-coupled-max-spread = <550000 550000>;
regulator-always-on;

nvidia,tegra-cpu-regulator;
@@ -425,10 +448,10 @@ ldo1 {

rtc_vdd_reg: ldo2 {
regulator-name = "+1.2vs_ldo2,vdd_rtc";
- regulator-min-microvolt = <1200000>;
- regulator-max-microvolt = <1225000>;
+ regulator-min-microvolt = <950000>;
+ regulator-max-microvolt = <1300000>;
regulator-coupled-with = <&core_vdd_reg &cpu_vdd_reg>;
- regulator-coupled-max-spread = <170000 450000>;
+ regulator-coupled-max-spread = <170000 550000>;
regulator-always-on;

nvidia,tegra-rtc-regulator;
@@ -517,6 +540,7 @@ usb@c5000000 {
compatible = "nvidia,tegra20-udc";
status = "okay";
dr_mode = "peripheral";
+ core-supply = <&core_vdd_reg>;
};

usb-phy@c5000000 {
@@ -527,6 +551,7 @@ usb@c5004000 {
status = "okay";
nvidia,phy-reset-gpio = <&gpio TEGRA_GPIO(V, 0)
GPIO_ACTIVE_LOW>;
+ core-supply = <&core_vdd_reg>;
};

usb-phy@c5004000 {
@@ -537,6 +562,7 @@ usb-phy@c5004000 {

usb@c5008000 {
status = "okay";
+ core-supply = <&core_vdd_reg>;
};

usb-phy@c5008000 {
@@ -549,12 +575,14 @@ mmc@c8000000 {
wp-gpios = <&gpio TEGRA_GPIO(H, 1) GPIO_ACTIVE_HIGH>;
power-gpios = <&gpio TEGRA_GPIO(V, 1) GPIO_ACTIVE_HIGH>;
bus-width = <4>;
+ core-supply = <&core_vdd_reg>;
};

mmc@c8000600 {
status = "okay";
bus-width = <8>;
non-removable;
+ core-supply = <&core_vdd_reg>;
};

backlight: backlight {
@@ -569,6 +597,10 @@ backlight: backlight {
backlight-boot-off;
};

+ core-domain {
+ power-supply = <&core_vdd_reg>;
+ };
+
clk32k_in: clock@0 {
compatible = "fixed-clock";
clock-frequency = <32768>;
diff --git a/arch/arm/boot/dts/tegra20-peripherals-opp.dtsi b/arch/arm/boot/dts/tegra20-peripherals-opp.dtsi
index b84afecea154..7e015cdfbc55 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,102 +48,1003 @@ 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@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 c24d4a37613e..b617c69465fc 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>;
@@ -792,6 +792,10 @@ backlight: backlight {
default-brightness-level = <6>;
};

+ core-domain {
+ power-supply = <&vdd_core>;
+ };
+
clk32k_in: clock@0 {
compatible = "fixed-clock";
clock-frequency = <32768>;
diff --git a/arch/arm/boot/dts/tegra20-tamonten.dtsi b/arch/arm/boot/dts/tegra20-tamonten.dtsi
index 95e6bccdb4f6..bead3c88dec4 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>;
@@ -502,6 +502,10 @@ mmc@c8000600 {
status = "okay";
};

+ core-domain {
+ power-supply = <&vdd_core>;
+ };
+
clk32k_in: clock@0 {
compatible = "fixed-clock";
clock-frequency = <32768>;
diff --git a/arch/arm/boot/dts/tegra20-trimslice.dts b/arch/arm/boot/dts/tegra20-trimslice.dts
index 4bc87bc0c2a4..afcf8ff56087 100644
--- a/arch/arm/boot/dts/tegra20-trimslice.dts
+++ b/arch/arm/boot/dts/tegra20-trimslice.dts
@@ -379,6 +379,10 @@ mmc@c8000600 {
bus-width = <4>;
};

+ core-domain {
+ power-supply = <&vdd_core>;
+ };
+
clk32k_in: clock@0 {
compatible = "fixed-clock";
clock-frequency = <32768>;
@@ -444,6 +448,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 055334ae3d28..0b03b3b0fd0c 100644
--- a/arch/arm/boot/dts/tegra20-ventana.dts
+++ b/arch/arm/boot/dts/tegra20-ventana.dts
@@ -420,7 +420,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>;
@@ -587,6 +587,10 @@ backlight: backlight {
default-brightness-level = <6>;
};

+ core-domain {
+ power-supply = <&vdd_core>;
+ };
+
clk32k_in: clock@0 {
compatible = "fixed-clock";
clock-frequency = <32768>;
diff --git a/arch/arm/boot/dts/tegra20.dtsi b/arch/arm/boot/dts/tegra20.dtsi
index 6ce498178105..39963aa667c6 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,7 @@ mpe@54040000 {
clocks = <&tegra_car TEGRA20_CLK_MPE>;
resets = <&tegra_car 60>;
reset-names = "mpe";
+ power-domains = <&pd_mpe>;
};

vi@54080000 {
@@ -64,6 +67,7 @@ vi@54080000 {
clocks = <&tegra_car TEGRA20_CLK_VI>;
resets = <&tegra_car 20>;
reset-names = "vi";
+ power-domains = <&pd_venc>;
};

epp@540c0000 {
@@ -73,6 +77,7 @@ epp@540c0000 {
clocks = <&tegra_car TEGRA20_CLK_EPP>;
resets = <&tegra_car 19>;
reset-names = "epp";
+ power-domains = <&pd_core>;
};

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

gr2d@54140000 {
@@ -91,6 +97,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 +107,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 +120,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 +150,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>;

@@ -242,6 +256,169 @@ tegra_car: clock@60006000 {
reg = <0x60006000 0x1000>;
#clock-cells = <1>;
#reset-cells = <1>;
+
+ clocks {
+ 2d {
+ compatible = "nvidia,tegra20-clock";
+ operating-points-v2 = <&gr2d_dvfs_opp_table>;
+ clocks = <&tegra_car TEGRA20_CLK_GR2D>;
+ power-domains = <&pd_core>;
+ };
+
+ 3d {
+ compatible = "nvidia,tegra20-clock";
+ operating-points-v2 = <&gr3d_dvfs_opp_table>;
+ clocks = <&tegra_car TEGRA20_CLK_GR3D>;
+ power-domains = <&pd_core>;
+ };
+
+ dsi {
+ compatible = "nvidia,tegra20-clock";
+ operating-points-v2 = <&dsi_dvfs_opp_table>;
+ clocks = <&tegra_car TEGRA20_CLK_DSI>;
+ power-domains = <&pd_core>;
+ };
+
+ epp {
+ compatible = "nvidia,tegra20-clock";
+ operating-points-v2 = <&epp_dvfs_opp_table>;
+ clocks = <&tegra_car TEGRA20_CLK_EPP>;
+ power-domains = <&pd_core>;
+ };
+
+ hdmi {
+ compatible = "nvidia,tegra20-clock";
+ operating-points-v2 = <&hdmi_dvfs_opp_table>;
+ clocks = <&tegra_car TEGRA20_CLK_HDMI>;
+ power-domains = <&pd_core>;
+ };
+
+ host1x {
+ compatible = "nvidia,tegra20-clock";
+ operating-points-v2 = <&host1x_dvfs_opp_table>;
+ clocks = <&tegra_car TEGRA20_CLK_HOST1X>;
+ power-domains = <&pd_core>;
+ };
+
+ ide {
+ compatible = "nvidia,tegra20-clock";
+ operating-points-v2 = <&ide_dvfs_opp_table>;
+ clocks = <&tegra_car TEGRA20_CLK_IDE>;
+ power-domains = <&pd_core>;
+ };
+
+ mipi {
+ compatible = "nvidia,tegra20-clock";
+ operating-points-v2 = <&mipi_dvfs_opp_table>;
+ clocks = <&tegra_car TEGRA20_CLK_MIPI>;
+ power-domains = <&pd_core>;
+ };
+
+ mpe {
+ compatible = "nvidia,tegra20-clock";
+ operating-points-v2 = <&mpe_dvfs_opp_table>;
+ clocks = <&tegra_car TEGRA20_CLK_MPE>;
+ power-domains = <&pd_core>;
+ };
+
+ ndflash {
+ compatible = "nvidia,tegra20-clock";
+ operating-points-v2 = <&ndflash_dvfs_opp_table>;
+ clocks = <&tegra_car TEGRA20_CLK_NDFLASH>;
+ power-domains = <&pd_core>;
+ };
+
+ nor {
+ compatible = "nvidia,tegra20-clock";
+ operating-points-v2 = <&nor_dvfs_opp_table>;
+ clocks = <&tegra_car TEGRA20_CLK_NOR>;
+ power-domains = <&pd_core>;
+ };
+
+ pex {
+ compatible = "nvidia,tegra20-clock";
+ operating-points-v2 = <&pcie_dvfs_opp_table>;
+ clocks = <&tegra_car TEGRA20_CLK_PEX>;
+ power-domains = <&pd_core>;
+ };
+
+ sdmmc1 {
+ compatible = "nvidia,tegra20-clock";
+ operating-points-v2 = <&sdmmc1_dvfs_opp_table>;
+ clocks = <&tegra_car TEGRA20_CLK_SDMMC1>;
+ power-domains = <&pd_core>;
+ };
+
+ sdmmc2 {
+ compatible = "nvidia,tegra20-clock";
+ operating-points-v2 = <&sdmmc2_dvfs_opp_table>;
+ clocks = <&tegra_car TEGRA20_CLK_SDMMC2>;
+ power-domains = <&pd_core>;
+ };
+
+ sdmmc3 {
+ compatible = "nvidia,tegra20-clock";
+ operating-points-v2 = <&sdmmc3_dvfs_opp_table>;
+ clocks = <&tegra_car TEGRA20_CLK_SDMMC3>;
+ power-domains = <&pd_core>;
+ };
+
+ sdmmc4 {
+ compatible = "nvidia,tegra20-clock";
+ operating-points-v2 = <&sdmmc4_dvfs_opp_table>;
+ clocks = <&tegra_car TEGRA20_CLK_SDMMC4>;
+ power-domains = <&pd_core>;
+ };
+
+ sclk {
+ compatible = "nvidia,tegra20-clock";
+ operating-points-v2 = <&sclk_dvfs_opp_table>;
+ clocks = <&tegra_car TEGRA20_CLK_SCLK>;
+ power-domains = <&pd_core>;
+ };
+
+ tvo {
+ compatible = "nvidia,tegra20-clock";
+ operating-points-v2 = <&tvo_dvfs_opp_table>;
+ clocks = <&tegra_car TEGRA20_CLK_TVO>;
+ power-domains = <&pd_core>;
+ };
+
+ usbd {
+ compatible = "nvidia,tegra20-clock";
+ operating-points-v2 = <&usbd_dvfs_opp_table>;
+ clocks = <&tegra_car TEGRA20_CLK_USBD>;
+ power-domains = <&pd_core>;
+ };
+
+ usb2 {
+ compatible = "nvidia,tegra20-clock";
+ operating-points-v2 = <&usb2_dvfs_opp_table>;
+ clocks = <&tegra_car TEGRA20_CLK_USB2>;
+ power-domains = <&pd_core>;
+ };
+
+ usb3 {
+ compatible = "nvidia,tegra20-clock";
+ operating-points-v2 = <&usb3_dvfs_opp_table>;
+ clocks = <&tegra_car TEGRA20_CLK_USB3>;
+ power-domains = <&pd_core>;
+ };
+
+ vde {
+ compatible = "nvidia,tegra20-clock";
+ operating-points-v2 = <&vde_dvfs_opp_table>;
+ clocks = <&tegra_car TEGRA20_CLK_VDE>;
+ power-domains = <&pd_core>;
+ };
+
+ vi {
+ compatible = "nvidia,tegra20-clock";
+ operating-points-v2 = <&vi_dvfs_opp_table>;
+ clocks = <&tegra_car TEGRA20_CLK_VI>;
+ power-domains = <&pd_core>;
+ };
+ };
};

flow-controller@60007000 {
@@ -319,6 +496,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 {
@@ -643,6 +822,40 @@ tegra_pmc: pmc@7000e400 {
clocks = <&tegra_car TEGRA20_CLK_PCLK>, <&clk32k_in>;
clock-names = "pclk", "clk32k_in";
#clock-cells = <1>;
+
+ powergates {
+ pd_3d: 3d {
+ clocks = <&tegra_car TEGRA20_CLK_GR3D>;
+ resets = <&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 = <&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 = <&tegra_car TEGRA20_CLK_VDE>;
+ power-domains = <&pd_core>;
+ #power-domain-cells = <0>;
+ };
+
+ pd_mpe: mpe {
+ clocks = <&tegra_car TEGRA20_CLK_MPE>;
+ resets = <&tegra_car TEGRA20_CLK_MPE>;
+ power-domains = <&pd_core>;
+ #power-domain-cells = <0>;
+ };
+ };
};

mc: memory-controller@7000f000 {
@@ -662,6 +875,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>;
@@ -887,6 +1101,12 @@ mmc@c8000600 {
status = "disabled";
};

+ pd_core: core-domain {
+ compatible = "nvidia,tegra20-core-domain";
+ operating-points-v2 = <&core_opp_table>;
+ #power-domain-cells = <0>;
+ };
+
cpus {
#address-cells = <1>;
#size-cells = <0>;
--
2.29.2

2020-12-17 18:12:54

by Dmitry Osipenko

[permalink] [raw]
Subject: [PATCH v2 35/48] 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]>
Tested-by: Nicolas Chauvet <[email protected]>
Signed-off-by: Dmitry Osipenko <[email protected]>
---
drivers/gpu/drm/tegra/dc.c | 66 +++++++++++++++++++++++++++++++++++++-
1 file changed, 65 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/tegra/dc.c b/drivers/gpu/drm/tegra/dc.c
index b6676f1fe358..105ad786e432 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>
@@ -1699,6 +1702,48 @@ 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;
+
+ /* 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);
+
+ /* -ENOENT means that this device-tree doesn't have OPP table */
+ if (opp == ERR_PTR(-ENOENT))
+ return;
+
+ if (IS_ERR(opp)) {
+ dev_err(dc->dev, "failed to find OPP for %luHz: %pe\n",
+ rate, opp);
+ return;
+ }
+
+ pstate = dev_pm_opp_get_voltage(opp);
+ 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)
{
@@ -1738,6 +1783,8 @@ static void tegra_dc_commit_state(struct tegra_dc *dc,
if (err < 0)
dev_err(dc->dev, "failed to set clock %pC to %lu Hz: %d\n",
dc->clk, state->pclk, err);
+
+ tegra_dc_update_voltage_state(dc, state);
}

static void tegra_dc_stop(struct tegra_dc *dc)
@@ -1931,6 +1978,8 @@ 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);
+
+ dev_pm_genpd_set_performance_state(dc->dev, 0);
}

static void tegra_crtc_atomic_enable(struct drm_crtc *crtc,
@@ -2523,7 +2572,6 @@ static int tegra_dc_runtime_suspend(struct host1x_client *client)

clk_disable_unprepare(dc->clk);
pm_runtime_put_sync(dev);
-
return 0;
}

@@ -2881,6 +2929,18 @@ 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;
+
+ return 0;
+}
+
static int tegra_dc_probe(struct platform_device *pdev)
{
struct tegra_dc *dc;
@@ -2939,6 +2999,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);
--
2.29.2

2020-12-17 18:13:01

by Dmitry Osipenko

[permalink] [raw]
Subject: [PATCH v2 29/48] soc/tegra: pmc: Link domains to the parent Core domain

The Core domain is a parent of PMC power domains, hence PMC domains
should be set up as a sub-domains of the parent (Core) domain if
"power-domains" phandle presents in a device-tree node of PMC domain.

This allows to propagate GENPD performance changes to the parent Core
domain if performance change is applied to PMC domain.

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

diff --git a/drivers/soc/tegra/pmc.c b/drivers/soc/tegra/pmc.c
index 4f96dc7745c4..1a659d1c06d7 100644
--- a/drivers/soc/tegra/pmc.c
+++ b/drivers/soc/tegra/pmc.c
@@ -1236,6 +1236,7 @@ static int tegra_powergate_add(struct tegra_pmc *pmc, struct device_node *np)
static int tegra_powergate_init(struct tegra_pmc *pmc,
struct device_node *parent)
{
+ struct of_phandle_args child_args, parent_args;
struct device_node *np, *child;
int err = 0;

@@ -1249,6 +1250,24 @@ static int tegra_powergate_init(struct tegra_pmc *pmc,
of_node_put(child);
break;
}
+
+ if (of_parse_phandle_with_args(child, "power-domains",
+ "#power-domain-cells",
+ 0, &parent_args))
+ continue;
+
+ child_args.np = child;
+ child_args.args_count = 0;
+
+ err = of_genpd_add_subdomain(&parent_args, &child_args);
+ of_node_put(parent_args.np);
+ if (err) {
+ if (err == -ENOENT)
+ err = -EPROBE_DEFER;
+
+ of_node_put(child);
+ break;
+ }
}

of_node_put(np);
--
2.29.2

2020-12-17 18:13:03

by Dmitry Osipenko

[permalink] [raw]
Subject: [PATCH v2 39/48] drm/tegra: vic: Stop channel before suspending

Host1x channel should be idling before hardware is turned off, hence
stop the channel in the suspend callback.

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

diff --git a/drivers/gpu/drm/tegra/vic.c b/drivers/gpu/drm/tegra/vic.c
index ade56b860cf9..92d3d48c4277 100644
--- a/drivers/gpu/drm/tegra/vic.c
+++ b/drivers/gpu/drm/tegra/vic.c
@@ -510,8 +510,23 @@ static int vic_remove(struct platform_device *pdev)
return 0;
}

+static __maybe_unused int vic_suspend(struct device *dev)
+{
+ struct vic *vic = dev_get_drvdata(dev);
+ int err;
+
+ host1x_channel_stop(vic->channel);
+
+ err = pm_runtime_force_suspend(dev);
+ if (err < 0)
+ return err;
+
+ return 0;
+}
+
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(vic_suspend, pm_runtime_force_resume)
};

struct platform_driver tegra_vic_driver = {
--
2.29.2

2020-12-17 18:13:06

by Dmitry Osipenko

[permalink] [raw]
Subject: [PATCH v2 44/48] ARM: tegra: Add OPP tables and power domains to Tegra30 device-tree

Add OPP tables and power domains to Tegra30 device-tree.

Signed-off-by: Dmitry Osipenko <[email protected]>
---
.../tegra30-asus-nexus7-grouper-common.dtsi | 4 +
arch/arm/boot/dts/tegra30-beaver.dts | 4 +
arch/arm/boot/dts/tegra30-cardhu.dtsi | 20 +-
arch/arm/boot/dts/tegra30-colibri.dtsi | 20 +-
arch/arm/boot/dts/tegra30-ouya.dts | 4 +
.../arm/boot/dts/tegra30-peripherals-opp.dtsi | 1412 +++++++++++++++++
arch/arm/boot/dts/tegra30.dtsi | 358 +++++
7 files changed, 1816 insertions(+), 6 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 7dd2e9b592de..3ded32fdb381 100644
--- a/arch/arm/boot/dts/tegra30-asus-nexus7-grouper-common.dtsi
+++ b/arch/arm/boot/dts/tegra30-asus-nexus7-grouper-common.dtsi
@@ -1047,6 +1047,10 @@ battery_cell: battery-cell {
operating-range-celsius = <0 45>;
};

+ core-domain {
+ power-supply = <&vdd_core>;
+ };
+
/* PMIC has a built-in 32KHz oscillator which is used by PMC */
clk32k_in: clock@0 {
compatible = "fixed-clock";
diff --git a/arch/arm/boot/dts/tegra30-beaver.dts b/arch/arm/boot/dts/tegra30-beaver.dts
index e0624b74fb50..bcf421cb7f45 100644
--- a/arch/arm/boot/dts/tegra30-beaver.dts
+++ b/arch/arm/boot/dts/tegra30-beaver.dts
@@ -1965,6 +1965,10 @@ usb-phy@7d008000 {
status = "okay";
};

+ core-domain {
+ power-supply = <&core_vdd_reg>;
+ };
+
clk32k_in: clock@0 {
compatible = "fixed-clock";
clock-frequency = <32768>;
diff --git a/arch/arm/boot/dts/tegra30-cardhu.dtsi b/arch/arm/boot/dts/tegra30-cardhu.dtsi
index dab9989fa760..d74c9ca78a7f 100644
--- a/arch/arm/boot/dts/tegra30-cardhu.dtsi
+++ b/arch/arm/boot/dts/tegra30-cardhu.dtsi
@@ -272,9 +272,14 @@ vdd2_reg: vdd2 {

vddctrl_reg: vddctrl {
regulator-name = "vdd_cpu,vdd_sys";
- regulator-min-microvolt = <1000000>;
- regulator-max-microvolt = <1000000>;
+ 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;
};

vio_reg: vio {
@@ -342,17 +347,22 @@ temperature-sensor@4c {
interrupts = <TEGRA_GPIO(CC, 2) IRQ_TYPE_LEVEL_LOW>;
};

- tps62361@60 {
+ vdd_core: tps62361@60 {
compatible = "ti,tps62361";
reg = <0x60>;

regulator-name = "tps62361-vout";
regulator-min-microvolt = <500000>;
regulator-max-microvolt = <1500000>;
+ regulator-coupled-with = <&vddctrl_reg>;
+ regulator-coupled-max-spread = <300000>;
+ regulator-max-step-microvolt = <100000>;
regulator-boot-on;
regulator-always-on;
ti,vsel0-state-high;
ti,vsel1-state-high;
+
+ nvidia,tegra-core-regulator;
};
};

@@ -418,6 +428,10 @@ backlight: backlight {
default-brightness-level = <6>;
};

+ core-domain {
+ power-supply = <&vdd_core>;
+ };
+
clk32k_in: clock@0 {
compatible = "fixed-clock";
clock-frequency = <32768>;
diff --git a/arch/arm/boot/dts/tegra30-colibri.dtsi b/arch/arm/boot/dts/tegra30-colibri.dtsi
index e36aa3ce6c3d..96ad01f9c89a 100644
--- a/arch/arm/boot/dts/tegra30-colibri.dtsi
+++ b/arch/arm/boot/dts/tegra30-colibri.dtsi
@@ -764,9 +764,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 {
@@ -889,18 +894,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;
};
};

@@ -960,6 +970,10 @@ usb-phy@7d004000 {
vbus-supply = <&reg_lan_v_bus>;
};

+ core-domain {
+ power-supply = <&vdd_core>;
+ };
+
clk32k_in: xtal1 {
compatible = "fixed-clock";
#clock-cells = <0>;
diff --git a/arch/arm/boot/dts/tegra30-ouya.dts b/arch/arm/boot/dts/tegra30-ouya.dts
index 74da1360d297..0223328bdfed 100644
--- a/arch/arm/boot/dts/tegra30-ouya.dts
+++ b/arch/arm/boot/dts/tegra30-ouya.dts
@@ -377,6 +377,10 @@ usb-phy@7d008000 {
status = "okay";
};

+ core-domain {
+ power-supply = <&vdd_core>;
+ };
+
/* PMIC has a built-in 32KHz oscillator which is used by PMC */
clk32k_in: clock {
compatible = "fixed-clock";
diff --git a/arch/arm/boot/dts/tegra30-peripherals-opp.dtsi b/arch/arm/boot/dts/tegra30-peripherals-opp.dtsi
index cbe84d25e726..983db1a06682 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,258 +58,301 @@ 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@204000000,1250 {
opp-microvolt = <1250000 1250000 1350000>;
opp-hz = /bits/ 64 <204000000>;
opp-supported-hw = <0x0008>;
+ required-opps = <&core_opp_1250>;
};

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>;
};
};

@@ -380,4 +473,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: pll_c-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: pll_e-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: pll_m-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 44a6dbba7081..fdf7b66cb92d 100644
--- a/arch/arm/boot/dts/tegra30.dtsi
+++ b/arch/arm/boot/dts/tegra30.dtsi
@@ -123,6 +123,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>;
@@ -136,6 +138,7 @@ mpe@54040000 {
clocks = <&tegra_car TEGRA30_CLK_MPE>;
resets = <&tegra_car 60>;
reset-names = "mpe";
+ power-domains = <&pd_mpe>;

iommus = <&mc TEGRA_SWGROUP_MPE>;
};
@@ -147,6 +150,7 @@ vi@54080000 {
clocks = <&tegra_car TEGRA30_CLK_VI>;
resets = <&tegra_car 20>;
reset-names = "vi";
+ power-domains = <&pd_venc>;

iommus = <&mc TEGRA_SWGROUP_VI>;
};
@@ -158,6 +162,7 @@ epp@540c0000 {
clocks = <&tegra_car TEGRA30_CLK_EPP>;
resets = <&tegra_car 19>;
reset-names = "epp";
+ power-domains = <&pd_heg>;

iommus = <&mc TEGRA_SWGROUP_EPP>;
};
@@ -169,6 +174,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>;
};
@@ -180,6 +186,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>;
};
@@ -193,6 +201,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>;
@@ -207,6 +218,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>;

@@ -237,6 +250,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>;

@@ -357,6 +372,288 @@ tegra_car: clock@60006000 {
reg = <0x60006000 0x1000>;
#clock-cells = <1>;
#reset-cells = <1>;
+
+ clocks {
+ 2d {
+ compatible = "nvidia,tegra30-clock";
+ operating-points-v2 = <&gr2d_dvfs_opp_table>;
+ clocks = <&tegra_car TEGRA30_CLK_GR2D>;
+ power-domains = <&pd_core>;
+ };
+
+ 3d {
+ compatible = "nvidia,tegra30-clock";
+ operating-points-v2 = <&gr3d_dvfs_opp_table>;
+ clocks = <&tegra_car TEGRA30_CLK_GR3D>;
+ power-domains = <&pd_core>;
+ };
+
+ 3d2 {
+ compatible = "nvidia,tegra30-clock";
+ operating-points-v2 = <&gr3d_dvfs_opp_table>;
+ clocks = <&tegra_car TEGRA30_CLK_GR3D2>;
+ power-domains = <&pd_core>;
+ };
+
+ afi {
+ compatible = "nvidia,tegra30-clock";
+ operating-points-v2 = <&afi_dvfs_opp_table>;
+ clocks = <&tegra_car TEGRA30_CLK_AFI>;
+ power-domains = <&pd_core>;
+ };
+
+ cve {
+ compatible = "nvidia,tegra30-clock";
+ operating-points-v2 = <&cve_dvfs_opp_table>;
+ clocks = <&tegra_car TEGRA30_CLK_CVE>;
+ power-domains = <&pd_core>;
+ };
+
+ dsia {
+ compatible = "nvidia,tegra30-clock";
+ operating-points-v2 = <&dsia_dvfs_opp_table>;
+ clocks = <&tegra_car TEGRA30_CLK_DSIA>;
+ power-domains = <&pd_core>;
+ };
+
+ dsib {
+ compatible = "nvidia,tegra30-clock";
+ operating-points-v2 = <&dsib_dvfs_opp_table>;
+ clocks = <&tegra_car TEGRA30_CLK_DSIB>;
+ power-domains = <&pd_core>;
+ };
+
+ epp {
+ compatible = "nvidia,tegra30-clock";
+ operating-points-v2 = <&epp_dvfs_opp_table>;
+ clocks = <&tegra_car TEGRA30_CLK_EPP>;
+ power-domains = <&pd_core>;
+ };
+
+ fuse_burn {
+ compatible = "nvidia,tegra30-clock";
+ operating-points-v2 = <&fuse_burn_dvfs_opp_table>;
+ clocks = <&tegra_car TEGRA30_CLK_FUSE_BURN>;
+ power-domains = <&pd_core>;
+ };
+
+ hdmi {
+ compatible = "nvidia,tegra30-clock";
+ operating-points-v2 = <&hdmi_dvfs_opp_table>;
+ clocks = <&tegra_car TEGRA30_CLK_HDMI>;
+ power-domains = <&pd_core>;
+ };
+
+ host1x {
+ compatible = "nvidia,tegra30-clock";
+ operating-points-v2 = <&host1x_dvfs_opp_table>;
+ clocks = <&tegra_car TEGRA30_CLK_HOST1X>;
+ power-domains = <&pd_core>;
+ };
+
+ mipi {
+ compatible = "nvidia,tegra30-clock";
+ operating-points-v2 = <&mipi_dvfs_opp_table>;
+ clocks = <&tegra_car TEGRA30_CLK_MIPI>;
+ power-domains = <&pd_core>;
+ };
+
+ mpe {
+ compatible = "nvidia,tegra30-clock";
+ operating-points-v2 = <&mpe_dvfs_opp_table>;
+ clocks = <&tegra_car TEGRA30_CLK_MPE>;
+ power-domains = <&pd_core>;
+ };
+
+ sclk {
+ compatible = "nvidia,tegra30-clock";
+ operating-points-v2 = <&sclk_dvfs_opp_table>;
+ clocks = <&tegra_car TEGRA30_CLK_SCLK>;
+ power-domains = <&pd_core>;
+ };
+
+ se {
+ compatible = "nvidia,tegra30-clock";
+ operating-points-v2 = <&se_dvfs_opp_table>;
+ clocks = <&tegra_car TEGRA30_CLK_SE>;
+ power-domains = <&pd_core>;
+ };
+
+ ndflash {
+ compatible = "nvidia,tegra30-clock";
+ operating-points-v2 = <&ndflash_dvfs_opp_table>;
+ clocks = <&tegra_car TEGRA30_CLK_NDFLASH>;
+ power-domains = <&pd_core>;
+ };
+
+ nor {
+ compatible = "nvidia,tegra30-clock";
+ operating-points-v2 = <&nor_dvfs_opp_table>;
+ clocks = <&tegra_car TEGRA30_CLK_NOR>;
+ power-domains = <&pd_core>;
+ };
+
+ pcie {
+ compatible = "nvidia,tegra30-clock";
+ operating-points-v2 = <&pcie_dvfs_opp_table>;
+ clocks = <&tegra_car TEGRA30_CLK_PCIE>;
+ power-domains = <&pd_core>;
+ };
+
+ pll_c {
+ compatible = "nvidia,tegra30-clock";
+ operating-points-v2 = <&pll_c_dvfs_opp_table>;
+ clocks = <&tegra_car TEGRA30_CLK_PLL_C>;
+ power-domains = <&pd_core>;
+ };
+
+ pll_e {
+ compatible = "nvidia,tegra30-clock";
+ operating-points-v2 = <&pll_e_dvfs_opp_table>;
+ clocks = <&tegra_car TEGRA30_CLK_PLL_E>;
+ power-domains = <&pd_core>;
+ };
+
+ pll_m {
+ compatible = "nvidia,tegra30-clock";
+ operating-points-v2 = <&pll_m_dvfs_opp_table>;
+ clocks = <&tegra_car TEGRA30_CLK_PLL_M>;
+ power-domains = <&pd_core>;
+ };
+
+ pwm {
+ compatible = "nvidia,tegra30-clock";
+ operating-points-v2 = <&pwm_dvfs_opp_table>;
+ clocks = <&tegra_car TEGRA30_CLK_PWM>;
+ power-domains = <&pd_core>;
+ };
+
+ sata {
+ compatible = "nvidia,tegra30-clock";
+ operating-points-v2 = <&sata_dvfs_opp_table>;
+ clocks = <&tegra_car TEGRA30_CLK_SATA>;
+ power-domains = <&pd_core>;
+ };
+
+ sata_oob {
+ compatible = "nvidia,tegra30-clock";
+ operating-points-v2 = <&sata_oob_dvfs_opp_table>;
+ clocks = <&tegra_car TEGRA30_CLK_SATA_OOB>;
+ power-domains = <&pd_core>;
+ };
+
+ sbc1 {
+ compatible = "nvidia,tegra30-clock";
+ operating-points-v2 = <&sbc1_dvfs_opp_table>;
+ clocks = <&tegra_car TEGRA30_CLK_SBC1>;
+ power-domains = <&pd_core>;
+ };
+
+ sbc2 {
+ compatible = "nvidia,tegra30-clock";
+ operating-points-v2 = <&sbc2_dvfs_opp_table>;
+ clocks = <&tegra_car TEGRA30_CLK_SBC2>;
+ power-domains = <&pd_core>;
+ };
+
+ sbc3 {
+ compatible = "nvidia,tegra30-clock";
+ operating-points-v2 = <&sbc3_dvfs_opp_table>;
+ clocks = <&tegra_car TEGRA30_CLK_SBC3>;
+ power-domains = <&pd_core>;
+ };
+
+ sbc4 {
+ compatible = "nvidia,tegra30-clock";
+ operating-points-v2 = <&sbc4_dvfs_opp_table>;
+ clocks = <&tegra_car TEGRA30_CLK_SBC4>;
+ power-domains = <&pd_core>;
+ };
+
+ sbc5 {
+ compatible = "nvidia,tegra30-clock";
+ operating-points-v2 = <&sbc5_dvfs_opp_table>;
+ clocks = <&tegra_car TEGRA30_CLK_SBC5>;
+ power-domains = <&pd_core>;
+ };
+
+ sbc6 {
+ compatible = "nvidia,tegra30-clock";
+ operating-points-v2 = <&sbc6_dvfs_opp_table>;
+ clocks = <&tegra_car TEGRA30_CLK_SBC6>;
+ power-domains = <&pd_core>;
+ };
+
+ sdmmc1 {
+ compatible = "nvidia,tegra30-clock";
+ operating-points-v2 = <&sdmmc1_dvfs_opp_table>;
+ clocks = <&tegra_car TEGRA30_CLK_SDMMC1>;
+ power-domains = <&pd_core>;
+ };
+
+ sdmmc3 {
+ compatible = "nvidia,tegra30-clock";
+ operating-points-v2 = <&sdmmc3_dvfs_opp_table>;
+ clocks = <&tegra_car TEGRA30_CLK_SDMMC3>;
+ power-domains = <&pd_core>;
+ };
+
+ spdif_out {
+ compatible = "nvidia,tegra30-clock";
+ operating-points-v2 = <&spdif_out_dvfs_opp_table>;
+ clocks = <&tegra_car TEGRA30_CLK_SPDIF_OUT>;
+ power-domains = <&pd_core>;
+ };
+
+ tvdac {
+ compatible = "nvidia,tegra30-clock";
+ operating-points-v2 = <&tvdac_dvfs_opp_table>;
+ clocks = <&tegra_car TEGRA30_CLK_TVDAC>;
+ power-domains = <&pd_core>;
+ };
+
+ tvo {
+ compatible = "nvidia,tegra30-clock";
+ operating-points-v2 = <&tvo_dvfs_opp_table>;
+ clocks = <&tegra_car TEGRA30_CLK_TVO>;
+ power-domains = <&pd_core>;
+ };
+
+ usbd {
+ compatible = "nvidia,tegra30-clock";
+ operating-points-v2 = <&usbd_dvfs_opp_table>;
+ clocks = <&tegra_car TEGRA30_CLK_USBD>;
+ power-domains = <&pd_core>;
+ };
+
+ usb2 {
+ compatible = "nvidia,tegra30-clock";
+ operating-points-v2 = <&usb2_dvfs_opp_table>;
+ clocks = <&tegra_car TEGRA30_CLK_USB2>;
+ power-domains = <&pd_core>;
+ };
+
+ usb3 {
+ compatible = "nvidia,tegra30-clock";
+ operating-points-v2 = <&usb3_dvfs_opp_table>;
+ clocks = <&tegra_car TEGRA30_CLK_USB3>;
+ power-domains = <&pd_core>;
+ };
+
+ vde {
+ compatible = "nvidia,tegra30-clock";
+ operating-points-v2 = <&vde_dvfs_opp_table>;
+ clocks = <&tegra_car TEGRA30_CLK_VDE>;
+ power-domains = <&pd_core>;
+ };
+
+ vi {
+ compatible = "nvidia,tegra30-clock";
+ operating-points-v2 = <&vi_dvfs_opp_table>;
+ clocks = <&tegra_car TEGRA30_CLK_VI>;
+ power-domains = <&pd_core>;
+ };
+ };
};

flow-controller@60007000 {
@@ -466,6 +763,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 {
@@ -763,6 +1062,58 @@ tegra_pmc: pmc@7000e400 {
clocks = <&tegra_car TEGRA30_CLK_PCLK>, <&clk32k_in>;
clock-names = "pclk", "clk32k_in";
#clock-cells = <1>;
+
+ powergates {
+ pd_3d0: 3d0 {
+ clocks = <&tegra_car TEGRA30_CLK_GR3D>;
+ resets = <&tegra_car TEGRA30_CLK_GR3D>;
+ power-domains = <&pd_core>;
+ #power-domain-cells = <0>;
+ };
+
+ pd_3d1: 3d1 {
+ clocks = <&tegra_car TEGRA30_CLK_GR3D2>;
+ resets = <&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 = <&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 = <&tegra_car TEGRA30_CLK_VDE>;
+ power-domains = <&pd_core>;
+ #power-domain-cells = <0>;
+ };
+
+ pd_mpe: mpe {
+ clocks = <&tegra_car TEGRA30_CLK_MPE>;
+ resets = <&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 = <&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 {
@@ -783,6 +1134,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>;
@@ -1057,6 +1409,12 @@ phy3: usb-phy@7d008000 {
status = "disabled";
};

+ pd_core: core-domain {
+ compatible = "nvidia,tegra30-core-domain";
+ operating-points-v2 = <&core_opp_table>;
+ #power-domain-cells = <0>;
+ };
+
cpus {
#address-cells = <1>;
#size-cells = <0>;
--
2.29.2

2020-12-17 18:13:09

by Dmitry Osipenko

[permalink] [raw]
Subject: [PATCH v2 41/48] memory: tegra20-emc: Use devm_tegra_core_dev_init_opp_table()

Use common devm_tegra_core_dev_init_opp_table() helper for the OPP table
initialization.

Signed-off-by: Dmitry Osipenko <[email protected]>
---
drivers/memory/tegra/tegra20-emc.c | 57 +++---------------------------
1 file changed, 4 insertions(+), 53 deletions(-)

diff --git a/drivers/memory/tegra/tegra20-emc.c b/drivers/memory/tegra/tegra20-emc.c
index 686aaf477d8a..4be847442fc7 100644
--- a/drivers/memory/tegra/tegra20-emc.c
+++ b/drivers/memory/tegra/tegra20-emc.c
@@ -908,58 +908,6 @@ static int tegra_emc_interconnect_init(struct tegra_emc *emc)
return err;
}

-static int tegra_emc_opp_table_init(struct tegra_emc *emc)
-{
- u32 hw_version = BIT(tegra_sku_info.soc_process_id);
- struct opp_table *clk_opp_table, *hw_opp_table;
- int err;
-
- clk_opp_table = dev_pm_opp_set_clkname(emc->dev, NULL);
- err = PTR_ERR_OR_ZERO(clk_opp_table);
- if (err) {
- dev_err(emc->dev, "failed to set OPP clk: %d\n", err);
- return err;
- }
-
- hw_opp_table = dev_pm_opp_set_supported_hw(emc->dev, &hw_version, 1);
- err = PTR_ERR_OR_ZERO(hw_opp_table);
- if (err) {
- dev_err(emc->dev, "failed to set OPP supported HW: %d\n", err);
- goto put_clk_table;
- }
-
- err = dev_pm_opp_of_add_table(emc->dev);
- if (err) {
- if (err == -ENODEV)
- dev_err(emc->dev, "OPP table not found, please update your device tree\n");
- else
- dev_err(emc->dev, "failed to add OPP table: %d\n", err);
-
- goto put_hw_table;
- }
-
- dev_info(emc->dev, "OPP HW ver. 0x%x, current clock rate %lu MHz\n",
- hw_version, clk_get_rate(emc->clk) / 1000000);
-
- /* first dummy rate-set initializes voltage state */
- err = dev_pm_opp_set_rate(emc->dev, clk_get_rate(emc->clk));
- if (err) {
- dev_err(emc->dev, "failed to initialize OPP clock: %d\n", err);
- goto remove_table;
- }
-
- return 0;
-
-remove_table:
- dev_pm_opp_of_remove_table(emc->dev);
-put_hw_table:
- dev_pm_opp_put_supported_hw(hw_opp_table);
-put_clk_table:
- dev_pm_opp_put_clkname(clk_opp_table);
-
- return err;
-}
-
static void devm_tegra_emc_unset_callback(void *data)
{
tegra20_clk_set_emc_round_callback(NULL, NULL);
@@ -1086,6 +1034,7 @@ static int tegra_emc_devfreq_init(struct tegra_emc *emc)

static int tegra_emc_probe(struct platform_device *pdev)
{
+ struct tegra_core_opp_params opp_params = {};
struct device_node *np;
struct tegra_emc *emc;
int irq, err;
@@ -1131,7 +1080,9 @@ static int tegra_emc_probe(struct platform_device *pdev)
if (err)
return err;

- err = tegra_emc_opp_table_init(emc);
+ opp_params.init_state = true;
+
+ err = devm_tegra_core_dev_init_opp_table(&pdev->dev, &opp_params);
if (err)
return err;

--
2.29.2

2020-12-17 18:13:16

by Dmitry Osipenko

[permalink] [raw]
Subject: [PATCH v2 36/48] drm/tegra: gr2d: Correct swapped device-tree compatibles

The device-tree compatibles are swapped in the code, correct them.

Tested-by: Peter Geis <[email protected]>
Tested-by: Nicolas Chauvet <[email protected]>
Signed-off-by: Dmitry Osipenko <[email protected]>
---
drivers/gpu/drm/tegra/gr2d.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/drivers/gpu/drm/tegra/gr2d.c b/drivers/gpu/drm/tegra/gr2d.c
index 1a0d3ba6e525..f30aa86e4c9f 100644
--- a/drivers/gpu/drm/tegra/gr2d.c
+++ b/drivers/gpu/drm/tegra/gr2d.c
@@ -162,8 +162,8 @@ static const struct gr2d_soc tegra30_gr2d_soc = {
};

static const struct of_device_id gr2d_match[] = {
- { .compatible = "nvidia,tegra30-gr2d", .data = &tegra20_gr2d_soc },
- { .compatible = "nvidia,tegra20-gr2d", .data = &tegra30_gr2d_soc },
+ { .compatible = "nvidia,tegra30-gr2d", .data = &tegra30_gr2d_soc },
+ { .compatible = "nvidia,tegra20-gr2d", .data = &tegra20_gr2d_soc },
{ },
};
MODULE_DEVICE_TABLE(of, gr2d_match);
--
2.29.2

2020-12-17 18:13:22

by Dmitry Osipenko

[permalink] [raw]
Subject: [PATCH v2 15/48] opp: Support set_opp() customization without requiring to use regulators

Support set_opp() customization without requiring to use regulators. This
is needed by drivers which want to use dev_pm_opp_set_rate() for changing
rates of a multiple clocks and don't need to touch regulator.

One example is NVIDIA Tegra30/114 SoCs which have two sibling 3D hardware
units which should be use to the same clock rate, meanwhile voltage
scaling is done using a power domain. In this case OPP table doesn't have
a regulator, causing a NULL dereference in _set_opp_custom().

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

diff --git a/drivers/opp/core.c b/drivers/opp/core.c
index 3d02fe33630b..625dae7a5ecb 100644
--- a/drivers/opp/core.c
+++ b/drivers/opp/core.c
@@ -828,17 +828,25 @@ static int _set_opp_custom(const struct opp_table *opp_table,
struct dev_pm_opp_supply *old_supply,
struct dev_pm_opp_supply *new_supply)
{
- struct dev_pm_set_opp_data *data;
+ struct dev_pm_set_opp_data *data, tmp_data;
+ unsigned int regulator_count;
int size;

- data = opp_table->set_opp_data;
+ if (opp_table->set_opp_data) {
+ data = opp_table->set_opp_data;
+ regulator_count = opp_table->regulator_count;
+ } else {
+ data = &tmp_data;
+ regulator_count = 0;
+ }
+
data->regulators = opp_table->regulators;
- data->regulator_count = opp_table->regulator_count;
+ data->regulator_count = regulator_count;
data->clk = opp_table->clk;
data->dev = dev;

data->old_opp.rate = old_freq;
- size = sizeof(*old_supply) * opp_table->regulator_count;
+ size = sizeof(*old_supply) * regulator_count;
if (!old_supply)
memset(data->old_opp.supplies, 0, size);
else
--
2.29.2

2020-12-17 18:13:22

by Dmitry Osipenko

[permalink] [raw]
Subject: [PATCH v2 37/48] drm/tegra: gr2d: Support OPP and power management

Add OPP and PM support to the GR2D driver. This is required for enabling
system-wide DVFS and supporting dynamic power management using a generic
power domain.

Tested-by: Peter Geis <[email protected]>
Tested-by: Nicolas Chauvet <[email protected]>
Signed-off-by: Dmitry Osipenko <[email protected]>
---
drivers/gpu/drm/tegra/gr2d.c | 73 +++++++++++++++++++++++++++++++++---
1 file changed, 67 insertions(+), 6 deletions(-)

diff --git a/drivers/gpu/drm/tegra/gr2d.c b/drivers/gpu/drm/tegra/gr2d.c
index f30aa86e4c9f..689554ef81a8 100644
--- a/drivers/gpu/drm/tegra/gr2d.c
+++ b/drivers/gpu/drm/tegra/gr2d.c
@@ -7,6 +7,9 @@
#include <linux/iommu.h>
#include <linux/module.h>
#include <linux/of_device.h>
+#include <linux/pm_runtime.h>
+
+#include <soc/tegra/common.h>

#include "drm.h"
#include "gem.h"
@@ -185,8 +188,15 @@ static const u32 gr2d_addr_regs[] = {
GR2D_VA_BASE_ADDR_SB,
};

+static void gr2d_pm_runtime_release(void *dev)
+{
+ pm_runtime_put(dev);
+ pm_runtime_disable(dev);
+}
+
static int gr2d_probe(struct platform_device *pdev)
{
+ struct tegra_core_opp_params opp_params = {};
struct device *dev = &pdev->dev;
struct host1x_syncpt **syncpts;
struct gr2d *gr2d;
@@ -197,6 +207,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);
@@ -209,12 +221,23 @@ 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");
+ opp_params.init_state = true;
+
+ err = devm_tegra_core_dev_init_opp_table(dev, &opp_params);
+ if (err && err != -ENODEV)
+ return err;
+
+ pm_runtime_enable(dev);
+ err = pm_runtime_get_sync(dev);
+ if (err < 0) {
+ gr2d_pm_runtime_release(dev);
return err;
}

+ err = devm_add_action_or_reset(dev, gr2d_pm_runtime_release, dev);
+ if (err)
+ return err;
+
INIT_LIST_HEAD(&gr2d->client.base.list);
gr2d->client.base.ops = &gr2d_client_ops;
gr2d->client.base.dev = dev;
@@ -229,7 +252,6 @@ static int gr2d_probe(struct platform_device *pdev)
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;
}

@@ -237,8 +259,6 @@ static int gr2d_probe(struct platform_device *pdev)
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;
}

@@ -254,15 +274,56 @@ static int gr2d_remove(struct platform_device *pdev)
return err;
}

+ return 0;
+}
+
+static int __maybe_unused gr2d_runtime_suspend(struct device *dev)
+{
+ struct gr2d *gr2d = dev_get_drvdata(dev);
+
clk_disable_unprepare(gr2d->clk);

return 0;
}

+static int __maybe_unused gr2d_runtime_resume(struct device *dev)
+{
+ struct gr2d *gr2d = dev_get_drvdata(dev);
+ int err;
+
+ err = clk_prepare_enable(gr2d->clk);
+ if (err) {
+ dev_err(dev, "failed to enable clock: %d\n", err);
+ return err;
+ }
+
+ return 0;
+}
+
+static __maybe_unused int gr2d_suspend(struct device *dev)
+{
+ struct gr2d *gr2d = dev_get_drvdata(dev);
+ int err;
+
+ host1x_channel_stop(gr2d->channel);
+
+ err = pm_runtime_force_suspend(dev);
+ if (err < 0)
+ return err;
+
+ return 0;
+}
+
+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(gr2d_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.29.2

2020-12-17 18:13:53

by Dmitry Osipenko

[permalink] [raw]
Subject: [PATCH v2 32/48] clk: tegra: Support runtime PM, power domain and OPP

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
rail, we need to hook up some clocks (which can't operate properly on a
lower voltages above certain clock rates) to the core power domain in
order to ensure that a proper core voltage is maintained.

The clocks are registered very early during kernel boot when the driver
core isn't available yet. Hence clk-device can't be created very early
and we need to split the registration of the clocks in two phases:

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

2. Register all clocks that use RPM (and not needed early) at a later
boot time, once driver core is available.

That's the reason why this patch reshuffles registration of a few clocks.

Signed-off-by: Dmitry Osipenko <[email protected]>
---
drivers/clk/tegra/Makefile | 1 +
drivers/clk/tegra/clk-device.c | 222 +++++++++++++++++++++++++++
drivers/clk/tegra/clk-divider.c | 2 +-
drivers/clk/tegra/clk-periph-gate.c | 2 +-
drivers/clk/tegra/clk-periph.c | 2 +-
drivers/clk/tegra/clk-pll.c | 2 +-
drivers/clk/tegra/clk-super.c | 4 +-
drivers/clk/tegra/clk-tegra-periph.c | 140 ++++++++++++-----
drivers/clk/tegra/clk-tegra114.c | 1 +
drivers/clk/tegra/clk-tegra124.c | 1 +
drivers/clk/tegra/clk-tegra20-emc.c | 2 +-
drivers/clk/tegra/clk-tegra20.c | 123 ++++++++-------
drivers/clk/tegra/clk-tegra210.c | 1 +
drivers/clk/tegra/clk-tegra30.c | 133 ++++++++--------
drivers/clk/tegra/clk.c | 89 +++++++++++
drivers/clk/tegra/clk.h | 7 +
16 files changed, 568 insertions(+), 164 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..be4143c4c99a
--- /dev/null
+++ b/drivers/clk/tegra/clk-device.c
@@ -0,0 +1,222 @@
+// 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"
+
+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;
+
+ mutex_lock(&clk_dev->lock);
+ if (clk_hw_is_enabled(clk_dev->hw)) {
+ 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);
+
+ err = clk_notifier_register(clk, &clk_dev->clk_nb);
+ if (err) {
+ dev_err(dev, "failed to register clk notifier: %d\n", err);
+ return err;
+ }
+
+ err = devm_tegra_core_dev_init_opp_table(dev, &opp_params);
+ if (err) {
+ clk_notifier_unregister(clk, &clk_dev->clk_nb);
+ return err;
+ }
+
+ /*
+ * The driver is attaching to a potentially active clock, hence we
+ * need to sync the power domain state in a accordance to the clock
+ * state.
+ */
+ err = tegra_clock_sync_pd_state(clk_dev);
+ if (err) {
+ clk_notifier_unregister(clk, &clk_dev->clk_nb);
+ return err;
+ }
+
+ return 0;
+}
+
+static int __maybe_unused tegra_clock_runtime_suspend(struct device *dev)
+{
+ return dev_pm_genpd_set_performance_state(dev, 0);
+}
+
+static int __maybe_unused tegra_clock_runtime_resume(struct device *dev)
+{
+ struct tegra_clk_device *clk_dev = dev_get_drvdata(dev);
+ unsigned long rate = clk_hw_get_rate(clk_dev->hw);
+
+ return tegra_clock_set_pd_state(clk_dev, rate);
+}
+
+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 it uses these clocks for toggling PD 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.
+ *
+ * In order to solve this problem, we will keep the clock resumed.
+ * This doesn't matter in regards to a power consumption because all
+ * SoC devices are power-gated, PLLs are disabled and even core power
+ * rail is completely turned off in a deepest suspend mode.
+ */
+
+ 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_RUNTIME_PM_OPS(tegra_clock_runtime_suspend,
+ tegra_clock_runtime_resume,
+ NULL)
+ 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-clock", },
+ { .compatible = "nvidia,tegra30-clock", },
+ { }
+};
+
+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-divider.c b/drivers/clk/tegra/clk-divider.c
index 38daf483ddf1..b11d47afc798 100644
--- a/drivers/clk/tegra/clk-divider.c
+++ b/drivers/clk/tegra/clk-divider.c
@@ -163,7 +163,7 @@ struct clk *tegra_clk_register_divider(const char *name,
/* Data in .init is copied by clk_register(), so stack variable OK */
divider->hw.init = &init;

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

diff --git a/drivers/clk/tegra/clk-periph-gate.c b/drivers/clk/tegra/clk-periph-gate.c
index 3c4259fec82e..b0bebf597b33 100644
--- a/drivers/clk/tegra/clk-periph-gate.c
+++ b/drivers/clk/tegra/clk-periph-gate.c
@@ -176,7 +176,7 @@ struct clk *tegra_clk_register_periph_gate(const char *name,
/* Data in .init is copied by clk_register(), so stack variable OK */
gate->hw.init = &init;

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

diff --git a/drivers/clk/tegra/clk-periph.c b/drivers/clk/tegra/clk-periph.c
index 79ca3aa072b7..f7affeba829b 100644
--- a/drivers/clk/tegra/clk-periph.c
+++ b/drivers/clk/tegra/clk-periph.c
@@ -195,7 +195,7 @@ static struct clk *_tegra_clk_register_periph(const char *name,
periph->gate.regs = bank;
periph->gate.enable_refcnt = periph_clk_enb_refcnt;

- clk = clk_register(NULL, &periph->hw);
+ clk = tegra_clk_register(&periph->hw);
if (IS_ERR(clk))
return clk;

diff --git a/drivers/clk/tegra/clk-pll.c b/drivers/clk/tegra/clk-pll.c
index d709ecb7d8d7..eee2e64e251b 100644
--- a/drivers/clk/tegra/clk-pll.c
+++ b/drivers/clk/tegra/clk-pll.c
@@ -1911,7 +1911,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_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..b2db6cf7d9db 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_register(&super->hw);
if (IS_ERR(clk))
kfree(super);

@@ -266,7 +266,7 @@ struct clk *tegra_clk_register_super_clk(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_register(&super->hw);
if (IS_ERR(clk))
kfree(super);

diff --git a/drivers/clk/tegra/clk-tegra-periph.c b/drivers/clk/tegra/clk-tegra-periph.c
index 60cc34f90cb9..c401e5f6d504 100644
--- a/drivers/clk/tegra/clk-tegra-periph.c
+++ b/drivers/clk/tegra/clk-tegra-periph.c
@@ -612,13 +612,6 @@ static struct tegra_periph_init_data periph_clks[] = {
I2C("i2c4", mux_pllp_clkm, CLK_SOURCE_I2C4, 103, tegra_clk_i2c4),
I2C("i2c5", mux_pllp_clkm, CLK_SOURCE_I2C5, 47, tegra_clk_i2c5),
I2C("i2c6", mux_pllp_clkm, CLK_SOURCE_I2C6, 166, tegra_clk_i2c6),
- INT("vde", mux_pllp_pllc_pllm_clkm, CLK_SOURCE_VDE, 61, 0, tegra_clk_vde),
- INT("vi", mux_pllm_pllc_pllp_plla, CLK_SOURCE_VI, 20, 0, tegra_clk_vi),
- INT("epp", mux_pllm_pllc_pllp_plla, CLK_SOURCE_EPP, 19, 0, tegra_clk_epp),
- INT("host1x", mux_pllm_pllc_pllp_plla, CLK_SOURCE_HOST1X, 28, 0, tegra_clk_host1x),
- INT("mpe", mux_pllm_pllc_pllp_plla, CLK_SOURCE_MPE, 60, 0, tegra_clk_mpe),
- INT("2d", mux_pllm_pllc_pllp_plla, CLK_SOURCE_2D, 21, 0, tegra_clk_gr2d),
- INT("3d", mux_pllm_pllc_pllp_plla, CLK_SOURCE_3D, 24, 0, tegra_clk_gr3d),
INT8("vde", mux_pllp_pllc2_c_c3_pllm_clkm, CLK_SOURCE_VDE, 61, 0, tegra_clk_vde_8),
INT8("vi", mux_pllm_pllc2_c_c3_pllp_plla, CLK_SOURCE_VI, 20, 0, tegra_clk_vi_8),
INT8("vi", mux_pllm_pllc2_c_c3_pllp_plla_pllc4, CLK_SOURCE_VI, 20, 0, tegra_clk_vi_9),
@@ -629,7 +622,6 @@ static struct tegra_periph_init_data periph_clks[] = {
INT("tsec", mux_pllp_pllc_clkm, CLK_SOURCE_TSEC, 83, 0, tegra_clk_tsec_8),
INT8("host1x", mux_pllm_pllc2_c_c3_pllp_plla, CLK_SOURCE_HOST1X, 28, 0, tegra_clk_host1x_8),
INT8("host1x", mux_pllc4_out1_pllc_pllc4_out2_pllp_clkm_plla_pllc4_out0, CLK_SOURCE_HOST1X, 28, 0, tegra_clk_host1x_9),
- INT8("se", mux_pllp_pllc2_c_c3_pllm_clkm, CLK_SOURCE_SE, 127, TEGRA_PERIPH_ON_APB, tegra_clk_se),
INT8("se", mux_pllp_pllc2_c_c3_clkm, CLK_SOURCE_SE, 127, TEGRA_PERIPH_ON_APB, tegra_clk_se_10),
INT8("2d", mux_pllm_pllc2_c_c3_pllp_plla, CLK_SOURCE_2D, 21, 0, tegra_clk_gr2d_8),
INT8("3d", mux_pllm_pllc2_c_c3_pllp_plla, CLK_SOURCE_3D, 24, 0, tegra_clk_gr3d_8),
@@ -641,10 +633,8 @@ static struct tegra_periph_init_data periph_clks[] = {
MUX("i2s2", mux_pllaout0_audio2_2x_pllp_clkm, CLK_SOURCE_I2S2, 18, TEGRA_PERIPH_ON_APB, tegra_clk_i2s2),
MUX("i2s3", mux_pllaout0_audio3_2x_pllp_clkm, CLK_SOURCE_I2S3, 101, TEGRA_PERIPH_ON_APB, tegra_clk_i2s3),
MUX("i2s4", mux_pllaout0_audio4_2x_pllp_clkm, CLK_SOURCE_I2S4, 102, TEGRA_PERIPH_ON_APB, tegra_clk_i2s4),
- MUX("spdif_out", mux_pllaout0_audio_2x_pllp_clkm, CLK_SOURCE_SPDIF_OUT, 10, TEGRA_PERIPH_ON_APB, tegra_clk_spdif_out),
MUX("spdif_in", mux_pllp_pllc_pllm, CLK_SOURCE_SPDIF_IN, 10, TEGRA_PERIPH_ON_APB, tegra_clk_spdif_in),
MUX8("spdif_in", mux_pllp_pllc_clkm_1, CLK_SOURCE_SPDIF_IN, 10, TEGRA_PERIPH_ON_APB, tegra_clk_spdif_in_8),
- MUX("pwm", mux_pllp_pllc_clk32_clkm, CLK_SOURCE_PWM, 17, TEGRA_PERIPH_ON_APB, tegra_clk_pwm),
MUX("adx", mux_plla_pllc_pllp_clkm, CLK_SOURCE_ADX, 154, TEGRA_PERIPH_ON_APB, tegra_clk_adx),
MUX("amx", mux_plla_pllc_pllp_clkm, CLK_SOURCE_AMX, 153, TEGRA_PERIPH_ON_APB, tegra_clk_amx),
MUX("hda", mux_pllp_pllc_pllm_clkm, CLK_SOURCE_HDA, 125, TEGRA_PERIPH_ON_APB, tegra_clk_hda),
@@ -652,18 +642,12 @@ static struct tegra_periph_init_data periph_clks[] = {
MUX("hda2codec_2x", mux_pllp_pllc_pllm_clkm, CLK_SOURCE_HDA2CODEC_2X, 111, TEGRA_PERIPH_ON_APB, tegra_clk_hda2codec_2x),
MUX8("hda2codec_2x", mux_pllp_pllc_plla_clkm, CLK_SOURCE_HDA2CODEC_2X, 111, TEGRA_PERIPH_ON_APB, tegra_clk_hda2codec_2x_8),
MUX("vfir", mux_pllp_pllc_pllm_clkm, CLK_SOURCE_VFIR, 7, TEGRA_PERIPH_ON_APB, tegra_clk_vfir),
- MUX("sdmmc1", mux_pllp_pllc_pllm_clkm, CLK_SOURCE_SDMMC1, 14, TEGRA_PERIPH_ON_APB, tegra_clk_sdmmc1),
- MUX("sdmmc2", mux_pllp_pllc_pllm_clkm, CLK_SOURCE_SDMMC2, 9, TEGRA_PERIPH_ON_APB, tegra_clk_sdmmc2),
- MUX("sdmmc3", mux_pllp_pllc_pllm_clkm, CLK_SOURCE_SDMMC3, 69, TEGRA_PERIPH_ON_APB, tegra_clk_sdmmc3),
- MUX("sdmmc4", mux_pllp_pllc_pllm_clkm, CLK_SOURCE_SDMMC4, 15, TEGRA_PERIPH_ON_APB, tegra_clk_sdmmc4),
MUX8("sdmmc1", mux_pllp_pllc4_out2_pllc4_out1_clkm_pllc4_out0, CLK_SOURCE_SDMMC1, 14, TEGRA_PERIPH_ON_APB, tegra_clk_sdmmc1_9),
MUX8("sdmmc3", mux_pllp_pllc4_out2_pllc4_out1_clkm_pllc4_out0, CLK_SOURCE_SDMMC3, 69, TEGRA_PERIPH_ON_APB, tegra_clk_sdmmc3_9),
MUX("la", mux_pllp_pllc_pllm_clkm, CLK_SOURCE_LA, 76, TEGRA_PERIPH_ON_APB, tegra_clk_la),
MUX("trace", mux_pllp_pllc_pllm_clkm, CLK_SOURCE_TRACE, 77, TEGRA_PERIPH_ON_APB, tegra_clk_trace),
MUX("owr", mux_pllp_pllc_pllm_clkm, CLK_SOURCE_OWR, 71, TEGRA_PERIPH_ON_APB, tegra_clk_owr),
MUX("owr", mux_pllp_pllc_clkm, CLK_SOURCE_OWR, 71, TEGRA_PERIPH_ON_APB, tegra_clk_owr_8),
- MUX("nor", mux_pllp_pllc_pllm_clkm, CLK_SOURCE_NOR, 42, 0, tegra_clk_nor),
- MUX("mipi", mux_pllp_pllc_pllm_clkm, CLK_SOURCE_MIPI, 50, TEGRA_PERIPH_ON_APB, tegra_clk_mipi),
MUX("vi_sensor", mux_pllm_pllc_pllp_plla, CLK_SOURCE_VI_SENSOR, 20, TEGRA_PERIPH_NO_RESET, tegra_clk_vi_sensor),
MUX("vi_sensor", mux_pllc_pllp_plla, CLK_SOURCE_VI_SENSOR, 20, TEGRA_PERIPH_NO_RESET, tegra_clk_vi_sensor_9),
MUX("cilab", mux_pllp_pllc_clkm, CLK_SOURCE_CILAB, 144, 0, tegra_clk_cilab),
@@ -676,20 +660,8 @@ static struct tegra_periph_init_data periph_clks[] = {
MUX("dfll_ref", mux_pllp_clkm, CLK_SOURCE_DFLL_REF, 155, TEGRA_PERIPH_ON_APB, tegra_clk_dfll_ref),
MUX("dfll_soc", mux_pllp_clkm, CLK_SOURCE_DFLL_SOC, 155, TEGRA_PERIPH_ON_APB, tegra_clk_dfll_soc),
MUX("i2cslow", mux_pllp_pllc_clk32_clkm, CLK_SOURCE_I2CSLOW, 81, TEGRA_PERIPH_ON_APB, tegra_clk_i2cslow),
- MUX("sbc1", mux_pllp_pllc_pllm_clkm, CLK_SOURCE_SBC1, 41, TEGRA_PERIPH_ON_APB, tegra_clk_sbc1),
- MUX("sbc2", mux_pllp_pllc_pllm_clkm, CLK_SOURCE_SBC2, 44, TEGRA_PERIPH_ON_APB, tegra_clk_sbc2),
- MUX("sbc3", mux_pllp_pllc_pllm_clkm, CLK_SOURCE_SBC3, 46, TEGRA_PERIPH_ON_APB, tegra_clk_sbc3),
- MUX("sbc4", mux_pllp_pllc_pllm_clkm, CLK_SOURCE_SBC4, 68, TEGRA_PERIPH_ON_APB, tegra_clk_sbc4),
- MUX("sbc5", mux_pllp_pllc_pllm_clkm, CLK_SOURCE_SBC5, 104, TEGRA_PERIPH_ON_APB, tegra_clk_sbc5),
- MUX("sbc6", mux_pllp_pllc_pllm_clkm, CLK_SOURCE_SBC6, 105, TEGRA_PERIPH_ON_APB, tegra_clk_sbc6),
- MUX("cve", mux_pllp_plld_pllc_clkm, CLK_SOURCE_CVE, 49, 0, tegra_clk_cve),
- MUX("tvo", mux_pllp_plld_pllc_clkm, CLK_SOURCE_TVO, 49, 0, tegra_clk_tvo),
- MUX("tvdac", mux_pllp_plld_pllc_clkm, CLK_SOURCE_TVDAC, 53, 0, tegra_clk_tvdac),
- MUX("ndflash", mux_pllp_pllc_pllm_clkm, CLK_SOURCE_NDFLASH, 13, TEGRA_PERIPH_ON_APB, tegra_clk_ndflash),
MUX("ndspeed", mux_pllp_pllc_pllm_clkm, CLK_SOURCE_NDSPEED, 80, TEGRA_PERIPH_ON_APB, tegra_clk_ndspeed),
- MUX("sata_oob", mux_pllp_pllc_pllm_clkm, CLK_SOURCE_SATA_OOB, 123, TEGRA_PERIPH_ON_APB, tegra_clk_sata_oob),
MUX("sata_oob", mux_pllp_pllc_clkm, CLK_SOURCE_SATA_OOB, 123, TEGRA_PERIPH_ON_APB, tegra_clk_sata_oob_8),
- MUX("sata", mux_pllp_pllc_pllm_clkm, CLK_SOURCE_SATA, 124, TEGRA_PERIPH_ON_APB, tegra_clk_sata),
MUX("sata", mux_pllp_pllc_clkm, CLK_SOURCE_SATA, 124, TEGRA_PERIPH_ON_APB, tegra_clk_sata_8),
MUX("adx1", mux_plla_pllc_pllp_clkm, CLK_SOURCE_ADX1, 180, TEGRA_PERIPH_ON_APB, tegra_clk_adx1),
MUX("amx1", mux_plla_pllc_pllp_clkm, CLK_SOURCE_AMX1, 185, TEGRA_PERIPH_ON_APB, tegra_clk_amx1),
@@ -711,7 +683,6 @@ static struct tegra_periph_init_data periph_clks[] = {
MUX("sbc4", mux_pllp_pllc_clkm, CLK_SOURCE_SBC4, 68, TEGRA_PERIPH_ON_APB, tegra_clk_sbc4_9),
MUX8("ndflash", mux_pllp_pllc2_c_c3_pllm_clkm, CLK_SOURCE_NDFLASH, 13, TEGRA_PERIPH_ON_APB, tegra_clk_ndflash_8),
MUX8("ndspeed", mux_pllp_pllc2_c_c3_pllm_clkm, CLK_SOURCE_NDSPEED, 80, TEGRA_PERIPH_ON_APB, tegra_clk_ndspeed_8),
- MUX8("hdmi", mux_pllp_pllm_plld_plla_pllc_plld2_clkm, CLK_SOURCE_HDMI, 51, 0, tegra_clk_hdmi),
MUX8("extern1", mux_plla_clk32_pllp_clkm_plle, CLK_SOURCE_EXTERN1, 120, 0, tegra_clk_extern1),
MUX8("extern2", mux_plla_clk32_pllp_clkm_plle, CLK_SOURCE_EXTERN2, 121, 0, tegra_clk_extern2),
MUX8("extern3", mux_plla_clk32_pllp_clkm_plle, CLK_SOURCE_EXTERN3, 122, 0, tegra_clk_extern3),
@@ -727,9 +698,7 @@ static struct tegra_periph_init_data periph_clks[] = {
MUX8("clk72mhz", mux_pllp_out3_pllp_pllc_clkm, CLK_SOURCE_CLK72MHZ, 177, TEGRA_PERIPH_NO_RESET, tegra_clk_clk72Mhz_8),
MUX_FLAGS("csite", mux_pllp_pllc_pllm_clkm, CLK_SOURCE_CSITE, 73, TEGRA_PERIPH_ON_APB, tegra_clk_csite, CLK_IGNORE_UNUSED),
MUX_FLAGS("csite", mux_pllp_pllre_clkm, CLK_SOURCE_CSITE, 73, TEGRA_PERIPH_ON_APB, tegra_clk_csite_8, CLK_IGNORE_UNUSED),
- NODIV("disp1", mux_pllp_pllm_plld_plla_pllc_plld2_clkm, CLK_SOURCE_DISP1, 29, 7, 27, 0, tegra_clk_disp1, NULL),
NODIV("disp1", mux_pllp_plld_plld2_clkm, CLK_SOURCE_DISP1, 29, 7, 27, 0, tegra_clk_disp1_8, NULL),
- NODIV("disp2", mux_pllp_pllm_plld_plla_pllc_plld2_clkm, CLK_SOURCE_DISP2, 29, 7, 26, 0, tegra_clk_disp2, NULL),
NODIV("disp2", mux_pllp_plld_plld2_clkm, CLK_SOURCE_DISP2, 29, 7, 26, 0, tegra_clk_disp2_8, NULL),
UART("uarta", mux_pllp_pllc_pllm_clkm, CLK_SOURCE_UARTA, 6, tegra_clk_uarta),
UART("uartb", mux_pllp_pllc_pllm_clkm, CLK_SOURCE_UARTB, 7, tegra_clk_uartb),
@@ -769,6 +738,40 @@ static struct tegra_periph_init_data periph_clks[] = {
MUX8("dmic3", mux_dmic3, CLK_SOURCE_DMIC3, 197, TEGRA_PERIPH_ON_APB | TEGRA_PERIPH_NO_RESET, tegra_clk_dmic3),
};

+static struct tegra_periph_init_data rpm_periph_clks[] = {
+ INT("vde", mux_pllp_pllc_pllm_clkm, CLK_SOURCE_VDE, 61, 0, tegra_clk_vde),
+ INT("vi", mux_pllm_pllc_pllp_plla, CLK_SOURCE_VI, 20, 0, tegra_clk_vi),
+ INT("epp", mux_pllm_pllc_pllp_plla, CLK_SOURCE_EPP, 19, 0, tegra_clk_epp),
+ INT("host1x", mux_pllm_pllc_pllp_plla, CLK_SOURCE_HOST1X, 28, 0, tegra_clk_host1x),
+ INT("mpe", mux_pllm_pllc_pllp_plla, CLK_SOURCE_MPE, 60, 0, tegra_clk_mpe),
+ INT("2d", mux_pllm_pllc_pllp_plla, CLK_SOURCE_2D, 21, 0, tegra_clk_gr2d),
+ INT("3d", mux_pllm_pllc_pllp_plla, CLK_SOURCE_3D, 24, 0, tegra_clk_gr3d),
+ INT8("se", mux_pllp_pllc2_c_c3_pllm_clkm, CLK_SOURCE_SE, 127, TEGRA_PERIPH_ON_APB, tegra_clk_se),
+ MUX("nor", mux_pllp_pllc_pllm_clkm, CLK_SOURCE_NOR, 42, 0, tegra_clk_nor),
+ MUX("spdif_out", mux_pllaout0_audio_2x_pllp_clkm, CLK_SOURCE_SPDIF_OUT, 10, TEGRA_PERIPH_ON_APB, tegra_clk_spdif_out),
+ MUX("pwm", mux_pllp_pllc_clk32_clkm, CLK_SOURCE_PWM, 17, TEGRA_PERIPH_ON_APB, tegra_clk_pwm),
+ MUX("sdmmc1", mux_pllp_pllc_pllm_clkm, CLK_SOURCE_SDMMC1, 14, TEGRA_PERIPH_ON_APB, tegra_clk_sdmmc1),
+ MUX("sdmmc2", mux_pllp_pllc_pllm_clkm, CLK_SOURCE_SDMMC2, 9, TEGRA_PERIPH_ON_APB, tegra_clk_sdmmc2),
+ MUX("sdmmc3", mux_pllp_pllc_pllm_clkm, CLK_SOURCE_SDMMC3, 69, TEGRA_PERIPH_ON_APB, tegra_clk_sdmmc3),
+ MUX("sdmmc4", mux_pllp_pllc_pllm_clkm, CLK_SOURCE_SDMMC4, 15, TEGRA_PERIPH_ON_APB, tegra_clk_sdmmc4),
+ MUX("mipi", mux_pllp_pllc_pllm_clkm, CLK_SOURCE_MIPI, 50, TEGRA_PERIPH_ON_APB, tegra_clk_mipi),
+ MUX("sbc1", mux_pllp_pllc_pllm_clkm, CLK_SOURCE_SBC1, 41, TEGRA_PERIPH_ON_APB, tegra_clk_sbc1),
+ MUX("sbc2", mux_pllp_pllc_pllm_clkm, CLK_SOURCE_SBC2, 44, TEGRA_PERIPH_ON_APB, tegra_clk_sbc2),
+ MUX("sbc3", mux_pllp_pllc_pllm_clkm, CLK_SOURCE_SBC3, 46, TEGRA_PERIPH_ON_APB, tegra_clk_sbc3),
+ MUX("sbc4", mux_pllp_pllc_pllm_clkm, CLK_SOURCE_SBC4, 68, TEGRA_PERIPH_ON_APB, tegra_clk_sbc4),
+ MUX("sbc5", mux_pllp_pllc_pllm_clkm, CLK_SOURCE_SBC5, 104, TEGRA_PERIPH_ON_APB, tegra_clk_sbc5),
+ MUX("sbc6", mux_pllp_pllc_pllm_clkm, CLK_SOURCE_SBC6, 105, TEGRA_PERIPH_ON_APB, tegra_clk_sbc6),
+ MUX("cve", mux_pllp_plld_pllc_clkm, CLK_SOURCE_CVE, 49, 0, tegra_clk_cve),
+ MUX("tvo", mux_pllp_plld_pllc_clkm, CLK_SOURCE_TVO, 49, 0, tegra_clk_tvo),
+ MUX("tvdac", mux_pllp_plld_pllc_clkm, CLK_SOURCE_TVDAC, 53, 0, tegra_clk_tvdac),
+ MUX("ndflash", mux_pllp_pllc_pllm_clkm, CLK_SOURCE_NDFLASH, 13, TEGRA_PERIPH_ON_APB, tegra_clk_ndflash),
+ MUX("sata_oob", mux_pllp_pllc_pllm_clkm, CLK_SOURCE_SATA_OOB, 123, TEGRA_PERIPH_ON_APB, tegra_clk_sata_oob),
+ MUX("sata", mux_pllp_pllc_pllm_clkm, CLK_SOURCE_SATA, 124, TEGRA_PERIPH_ON_APB, tegra_clk_sata),
+ MUX8("hdmi", mux_pllp_pllm_plld_plla_pllc_plld2_clkm, CLK_SOURCE_HDMI, 51, 0, tegra_clk_hdmi),
+ NODIV("disp1", mux_pllp_pllm_plld_plla_pllc_plld2_clkm, CLK_SOURCE_DISP1, 29, 7, 27, 0, tegra_clk_disp1, NULL),
+ NODIV("disp2", mux_pllp_pllm_plld_plla_pllc_plld2_clkm, CLK_SOURCE_DISP2, 29, 7, 26, 0, tegra_clk_disp2, NULL),
+};
+
static struct tegra_periph_init_data gate_clks[] = {
GATE("rtc", "clk_32k", 4, TEGRA_PERIPH_ON_APB | TEGRA_PERIPH_NO_RESET, tegra_clk_rtc, 0),
GATE("timer", "clk_m", 5, 0, tegra_clk_timer, CLK_IS_CRITICAL),
@@ -782,18 +785,13 @@ static struct tegra_periph_init_data gate_clks[] = {
* from LP1 system suspend and as part of CCPLEX cluster switching.
*/
GATE("fuse", "clk_m", 39, TEGRA_PERIPH_ON_APB, tegra_clk_fuse, CLK_IS_CRITICAL),
- GATE("fuse_burn", "clk_m", 39, TEGRA_PERIPH_ON_APB, tegra_clk_fuse_burn, 0),
GATE("kfuse", "clk_m", 40, TEGRA_PERIPH_ON_APB, tegra_clk_kfuse, 0),
GATE("apbif", "clk_m", 107, TEGRA_PERIPH_ON_APB, tegra_clk_apbif, 0),
GATE("hda2hdmi", "clk_m", 128, TEGRA_PERIPH_ON_APB, tegra_clk_hda2hdmi, 0),
GATE("bsea", "clk_m", 62, 0, tegra_clk_bsea, 0),
GATE("bsev", "clk_m", 63, 0, tegra_clk_bsev, 0),
GATE("mipi-cal", "clk72mhz", 56, 0, tegra_clk_mipi_cal, 0),
- GATE("usbd", "clk_m", 22, 0, tegra_clk_usbd, 0),
- GATE("usb2", "clk_m", 58, 0, tegra_clk_usb2, 0),
- GATE("usb3", "clk_m", 59, 0, tegra_clk_usb3, 0),
GATE("csi", "pll_p_out3", 52, 0, tegra_clk_csi, 0),
- GATE("afi", "mselect", 72, 0, tegra_clk_afi, 0),
GATE("csus", "clk_m", 92, TEGRA_PERIPH_NO_RESET, tegra_clk_csus, 0),
GATE("dds", "clk_m", 150, TEGRA_PERIPH_ON_APB, tegra_clk_dds, 0),
GATE("dp2", "clk_m", 152, TEGRA_PERIPH_ON_APB, tegra_clk_dp2, 0),
@@ -801,12 +799,10 @@ static struct tegra_periph_init_data gate_clks[] = {
GATE("xusb_host", "xusb_host_src", 89, 0, tegra_clk_xusb_host, 0),
GATE("xusb_ss", "xusb_ss_src", 156, 0, tegra_clk_xusb_ss, 0),
GATE("xusb_dev", "xusb_dev_src", 95, 0, tegra_clk_xusb_dev, 0),
- GATE("emc", "emc_mux", 57, 0, tegra_clk_emc, CLK_IS_CRITICAL),
GATE("sata_cold", "clk_m", 129, TEGRA_PERIPH_ON_APB, tegra_clk_sata_cold, 0),
GATE("ispa", "isp", 23, 0, tegra_clk_ispa, 0),
GATE("ispb", "isp", 3, 0, tegra_clk_ispb, 0),
GATE("vim2_clk", "clk_m", 11, 0, tegra_clk_vim2_clk, 0),
- GATE("pcie", "clk_m", 70, 0, tegra_clk_pcie, 0),
GATE("gpu", "pll_ref", 184, 0, tegra_clk_gpu, 0),
GATE("pllg_ref", "pll_ref", 189, 0, tegra_clk_pll_g_ref, 0),
GATE("hsic_trk", "usb2_hsic_trk", 209, TEGRA_PERIPH_NO_RESET, tegra_clk_hsic_trk, 0),
@@ -824,6 +820,15 @@ static struct tegra_periph_init_data gate_clks[] = {
GATE("adsp_neon", "aclk", 218, 0, tegra_clk_adsp_neon, 0),
};

+static struct tegra_periph_init_data rpm_gate_clks[] = {
+ GATE("fuse_burn", "clk_m", 39, TEGRA_PERIPH_ON_APB, tegra_clk_fuse_burn, 0),
+ GATE("usbd", "clk_m", 22, 0, tegra_clk_usbd, 0),
+ GATE("usb2", "clk_m", 58, 0, tegra_clk_usb2, 0),
+ GATE("usb3", "clk_m", 59, 0, tegra_clk_usb3, 0),
+ GATE("afi", "mselect", 72, 0, tegra_clk_afi, 0),
+ GATE("emc", "emc_mux", 57, 0, tegra_clk_emc, CLK_IS_CRITICAL),
+ GATE("pcie", "clk_m", 70, 0, tegra_clk_pcie, 0),
+};
static struct tegra_periph_init_data div_clks[] = {
DIV8("usb2_hsic_trk", "osc", CLK_SOURCE_USB2_HSIC_TRK, tegra_clk_usb2_hsic_trk, 0),
};
@@ -888,6 +893,33 @@ static void __init periph_clk_init(void __iomem *clk_base,
}
}

+static void __init rpm_periph_clk_init(void __iomem *clk_base,
+ struct tegra_clk *tegra_clks)
+{
+ int i;
+ struct clk *clk;
+ struct clk **dt_clk;
+
+ for (i = 0; i < ARRAY_SIZE(rpm_periph_clks); i++) {
+ const struct tegra_clk_periph_regs *bank;
+ struct tegra_periph_init_data *data;
+
+ data = rpm_periph_clks + i;
+
+ dt_clk = tegra_lookup_dt_id(data->clk_id, tegra_clks);
+ if (!dt_clk)
+ continue;
+
+ bank = get_reg_bank(data->periph.gate.clk_num);
+ if (!bank)
+ continue;
+
+ data->periph.gate.regs = bank;
+ clk = tegra_clk_register_periph_data(clk_base, data);
+ *dt_clk = clk;
+ }
+}
+
static void __init gate_clk_init(void __iomem *clk_base,
struct tegra_clk *tegra_clks)
{
@@ -913,6 +945,31 @@ static void __init gate_clk_init(void __iomem *clk_base,
}
}

+static void __init rpm_gate_clk_init(void __iomem *clk_base,
+ struct tegra_clk *tegra_clks)
+{
+ int i;
+ struct clk *clk;
+ struct clk **dt_clk;
+
+ for (i = 0; i < ARRAY_SIZE(rpm_gate_clks); i++) {
+ struct tegra_periph_init_data *data;
+
+ data = rpm_gate_clks + i;
+
+ dt_clk = tegra_lookup_dt_id(data->clk_id, tegra_clks);
+ if (!dt_clk)
+ continue;
+
+ clk = tegra_clk_register_periph_gate(data->name,
+ data->p.parent_name, data->periph.gate.flags,
+ clk_base, data->flags,
+ data->periph.gate.clk_num,
+ periph_clk_enb_refcnt);
+ *dt_clk = clk;
+ }
+}
+
static void __init div_clk_init(void __iomem *clk_base,
struct tegra_clk *tegra_clks)
{
@@ -1032,3 +1089,10 @@ void __init tegra_periph_clk_init(void __iomem *clk_base,
gate_clk_init(clk_base, tegra_clks);
div_clk_init(clk_base, tegra_clks);
}
+
+void __init tegra_periph_clk_rpm_init(void __iomem *clk_base,
+ struct tegra_clk *tegra_clks)
+{
+ rpm_periph_clk_init(clk_base, tegra_clks);
+ rpm_gate_clk_init(clk_base, tegra_clks);
+}
diff --git a/drivers/clk/tegra/clk-tegra114.c b/drivers/clk/tegra/clk-tegra114.c
index bc9e47a4cb60..9d90f1300370 100644
--- a/drivers/clk/tegra/clk-tegra114.c
+++ b/drivers/clk/tegra/clk-tegra114.c
@@ -1069,6 +1069,7 @@ static __init void tegra114_periph_clk_init(void __iomem *clk_base,

tegra_periph_clk_init(clk_base, pmc_base, tegra114_clks,
&pll_p_params);
+ tegra_periph_clk_rpm_init(clk_base, tegra114_clks);
}

/* Tegra114 CPU clock and reset control functions */
diff --git a/drivers/clk/tegra/clk-tegra124.c b/drivers/clk/tegra/clk-tegra124.c
index 934520aab6e3..0ca82f7e4628 100644
--- a/drivers/clk/tegra/clk-tegra124.c
+++ b/drivers/clk/tegra/clk-tegra124.c
@@ -1081,6 +1081,7 @@ static __init void tegra124_periph_clk_init(void __iomem *clk_base,
}

tegra_periph_clk_init(clk_base, pmc_base, tegra124_clks, &pll_p_params);
+ tegra_periph_clk_rpm_init(clk_base, tegra124_clks);
}

static void __init tegra124_pll_init(void __iomem *clk_base,
diff --git a/drivers/clk/tegra/clk-tegra20-emc.c b/drivers/clk/tegra/clk-tegra20-emc.c
index dd74b8543bf1..d2da6412775c 100644
--- a/drivers/clk/tegra/clk-tegra20-emc.c
+++ b/drivers/clk/tegra/clk-tegra20-emc.c
@@ -270,7 +270,7 @@ struct clk *tegra20_clk_register_emc(void __iomem *ioaddr, bool low_jitter)
emc->hw.init = &init;
emc->want_low_jitter = low_jitter;

- clk = clk_register(NULL, &emc->hw);
+ clk = tegra_clk_register(&emc->hw);
if (IS_ERR(clk)) {
kfree(emc);
return NULL;
diff --git a/drivers/clk/tegra/clk-tegra20.c b/drivers/clk/tegra/clk-tegra20.c
index 3efc651b42e3..c29d5af517c3 100644
--- a/drivers/clk/tegra/clk-tegra20.c
+++ b/drivers/clk/tegra/clk-tegra20.c
@@ -624,11 +624,6 @@ static void tegra20_pll_init(void)
{
struct clk *clk;

- /* PLLC */
- clk = tegra_clk_register_pll("pll_c", "pll_ref", clk_base, NULL, 0,
- &pll_c_params, NULL);
- clks[TEGRA20_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,
@@ -638,11 +633,6 @@ static void tegra20_pll_init(void)
0, NULL);
clks[TEGRA20_CLK_PLL_C_OUT1] = clk;

- /* PLLM */
- clk = tegra_clk_register_pll("pll_m", "pll_ref", clk_base, NULL,
- CLK_SET_RATE_GATE, &pll_m_params, NULL);
- clks[TEGRA20_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,
@@ -685,11 +675,6 @@ static void tegra20_pll_init(void)
clk_base + PLLA_OUT, 1, 0, CLK_IGNORE_UNUSED |
CLK_SET_RATE_PARENT, 0, NULL);
clks[TEGRA20_CLK_PLL_A_OUT0] = clk;
-
- /* PLLE */
- clk = tegra_clk_register_plle("pll_e", "pll_ref", clk_base, pmc_base,
- 0, &pll_e_params, NULL);
- clks[TEGRA20_CLK_PLL_E] = clk;
}

static const char *cclk_parents[] = { "clk_m", "pll_c", "clk_32k", "pll_m",
@@ -710,13 +695,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;
@@ -787,9 +765,7 @@ static struct tegra_periph_init_data tegra_periph_nodiv_clk_list[] = {

static void __init tegra20_periph_clk_init(void)
{
- struct tegra_periph_init_data *data;
struct clk *clk;
- unsigned int i;

/* ac97 */
clk = tegra_clk_register_periph_gate("ac97", "pll_a_out0",
@@ -797,26 +773,10 @@ static void __init tegra20_periph_clk_init(void)
clk_base, 0, 3, periph_clk_enb_refcnt);
clks[TEGRA20_CLK_AC97] = clk;

- /* emc */
- clk = tegra20_clk_register_emc(clk_base + CLK_SOURCE_EMC, false);
-
- clks[TEGRA20_CLK_EMC] = clk;
-
clk = tegra_clk_register_mc("mc", "emc", clk_base + CLK_SOURCE_EMC,
NULL);
clks[TEGRA20_CLK_MC] = clk;

- /* dsi */
- clk = tegra_clk_register_periph_gate("dsi", "pll_d", 0, clk_base, 0,
- 48, periph_clk_enb_refcnt);
- clk_register_clkdev(clk, NULL, "dsi");
- clks[TEGRA20_CLK_DSI] = clk;
-
- /* pex */
- clk = tegra_clk_register_periph_gate("pex", "clk_m", 0, clk_base, 0, 70,
- periph_clk_enb_refcnt);
- clks[TEGRA20_CLK_PEX] = clk;
-
/* dev1 OSC divider */
clk_register_divider(NULL, "dev1_osc_div", "clk_m",
0, clk_base + MISC_CLK_ENB, 22, 2,
@@ -839,21 +799,6 @@ static void __init tegra20_periph_clk_init(void)
clk_base, 0, 93, periph_clk_enb_refcnt);
clks[TEGRA20_CLK_CDEV2] = clk;

- for (i = 0; i < ARRAY_SIZE(tegra_periph_clk_list); i++) {
- data = &tegra_periph_clk_list[i];
- clk = tegra_clk_register_periph_data(clk_base, data);
- clks[data->clk_id] = clk;
- }
-
- for (i = 0; i < ARRAY_SIZE(tegra_periph_nodiv_clk_list); i++) {
- data = &tegra_periph_nodiv_clk_list[i];
- clk = tegra_clk_register_periph_nodiv(data->name,
- data->p.parent_names,
- data->num_parents, &data->periph,
- clk_base, data->offset);
- clks[data->clk_id] = clk;
- }
-
tegra_periph_clk_init(clk_base, pmc_base, tegra20_clks, &pll_p_params);
}

@@ -1111,6 +1056,70 @@ static struct clk *tegra20_clk_src_onecell_get(struct of_phandle_args *clkspec,
return clk;
}

+static void __init tegra20_clock_core_rpm_init(void)
+{
+ struct tegra_periph_init_data *data;
+ struct clk *clk;
+ unsigned int i;
+
+ /* PLLC */
+ clk = tegra_clk_register_pll("pll_c", "pll_ref", clk_base, NULL, 0,
+ &pll_c_params, NULL);
+ clks[TEGRA20_CLK_PLL_C] = clk;
+
+ /* PLLE */
+ clk = tegra_clk_register_plle("pll_e", "pll_ref", clk_base, pmc_base,
+ 0, &pll_e_params, NULL);
+ clks[TEGRA20_CLK_PLL_E] = clk;
+
+ /* PLLM */
+ clk = tegra_clk_register_pll("pll_m", "pll_ref", clk_base, NULL,
+ CLK_SET_RATE_GATE, &pll_m_params, NULL);
+ clks[TEGRA20_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[TEGRA20_CLK_SCLK] = clk;
+
+ /* dsi */
+ clk = tegra_clk_register_periph_gate("dsi", "pll_d", 0, clk_base, 0,
+ 48, periph_clk_enb_refcnt);
+ clk_register_clkdev(clk, NULL, "dsi");
+ clks[TEGRA20_CLK_DSI] = clk;
+
+ for (i = 0; i < ARRAY_SIZE(tegra_periph_clk_list); i++) {
+ data = &tegra_periph_clk_list[i];
+ clk = tegra_clk_register_periph_data(clk_base, data);
+ clks[data->clk_id] = clk;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(tegra_periph_nodiv_clk_list); i++) {
+ data = &tegra_periph_nodiv_clk_list[i];
+ clk = tegra_clk_register_periph_nodiv(data->name,
+ data->p.parent_names,
+ data->num_parents, &data->periph,
+ clk_base, data->offset);
+ clks[data->clk_id] = clk;
+ }
+
+ /* pex */
+ clk = tegra_clk_register_periph_gate("pex", "clk_m", 0, clk_base, 0, 70,
+ periph_clk_enb_refcnt);
+ clks[TEGRA20_CLK_PEX] = clk;
+
+ /* emc */
+ clk = tegra20_clk_register_emc(clk_base + CLK_SOURCE_EMC, false);
+
+ clks[TEGRA20_CLK_EMC] = clk;
+
+ tegra_periph_clk_rpm_init(clk_base, tegra20_clks);
+ tegra_init_dup_clks(tegra_clk_duplicates, clks, TEGRA20_CLK_CLK_MAX);
+ tegra_register_devclks(devclks, ARRAY_SIZE(devclks));
+}
+
static void __init tegra20_clock_init(struct device_node *np)
{
struct device_node *node;
@@ -1146,12 +1155,10 @@ static void __init tegra20_clock_init(struct device_node *np)
tegra20_periph_clk_init();
tegra20_audio_clk_init();

- 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_clk_apply_rpm_clocks = tegra20_clock_core_rpm_init;

tegra_cpu_car_ops = &tegra20_cpu_car_ops;
}
diff --git a/drivers/clk/tegra/clk-tegra210.c b/drivers/clk/tegra/clk-tegra210.c
index 68cbb98af567..c3fb0c98ee44 100644
--- a/drivers/clk/tegra/clk-tegra210.c
+++ b/drivers/clk/tegra/clk-tegra210.c
@@ -3131,6 +3131,7 @@ static __init void tegra210_periph_clk_init(struct device_node *np,
}

tegra_periph_clk_init(clk_base, pmc_base, tegra210_clks, &pll_p_params);
+ tegra_periph_clk_rpm_init(clk_base, tegra210_clks);

/* emc */
clk = tegra210_clk_register_emc(np, clk_base);
diff --git a/drivers/clk/tegra/clk-tegra30.c b/drivers/clk/tegra/clk-tegra30.c
index 98923c4674bf..3d48fd1e034a 100644
--- a/drivers/clk/tegra/clk-tegra30.c
+++ b/drivers/clk/tegra/clk-tegra30.c
@@ -812,11 +812,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 +821,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 +870,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 +958,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);
@@ -1018,29 +997,7 @@ static struct tegra_periph_init_data tegra_periph_nodiv_clk_list[] = {

static void __init tegra30_periph_clk_init(void)
{
- struct tegra_periph_init_data *data;
struct clk *clk;
- unsigned int i;
-
- /* dsia */
- clk = tegra_clk_register_periph_gate("dsia", "pll_d_out0", 0, clk_base,
- 0, 48, periph_clk_enb_refcnt);
- clks[TEGRA30_CLK_DSIA] = clk;
-
- /* pcie */
- clk = tegra_clk_register_periph_gate("pcie", "clk_m", 0, clk_base, 0,
- 70, periph_clk_enb_refcnt);
- clks[TEGRA30_CLK_PCIE] = clk;
-
- /* afi */
- clk = tegra_clk_register_periph_gate("afi", "clk_m", 0, clk_base, 0, 72,
- periph_clk_enb_refcnt);
- clks[TEGRA30_CLK_AFI] = clk;
-
- /* emc */
- clk = tegra20_clk_register_emc(clk_base + CLK_SOURCE_EMC, true);
-
- clks[TEGRA30_CLK_EMC] = clk;

clk = tegra_clk_register_mc("mc", "emc", clk_base + CLK_SOURCE_EMC,
NULL);
@@ -1056,21 +1013,6 @@ static void __init tegra30_periph_clk_init(void)
1, 0, &cml_lock);
clks[TEGRA30_CLK_CML1] = clk;

- for (i = 0; i < ARRAY_SIZE(tegra_periph_clk_list); i++) {
- data = &tegra_periph_clk_list[i];
- clk = tegra_clk_register_periph_data(clk_base, data);
- clks[data->clk_id] = clk;
- }
-
- for (i = 0; i < ARRAY_SIZE(tegra_periph_nodiv_clk_list); i++) {
- data = &tegra_periph_nodiv_clk_list[i];
- clk = tegra_clk_register_periph_nodiv(data->name,
- data->p.parent_names,
- data->num_parents, &data->periph,
- clk_base, data->offset);
- clks[data->clk_id] = clk;
- }
-
tegra_periph_clk_init(clk_base, pmc_base, tegra30_clks, &pll_p_params);
}

@@ -1315,6 +1257,77 @@ static struct clk *tegra30_clk_src_onecell_get(struct of_phandle_args *clkspec,
return clk;
}

+static void __init tegra30_clock_core_rpm_init(void)
+{
+ struct tegra_periph_init_data *data;
+ struct clk *clk;
+ unsigned int i;
+
+ /* 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;
+
+ /* dsia */
+ clk = tegra_clk_register_periph_gate("dsia", "pll_d_out0", 0, clk_base,
+ 0, 48, periph_clk_enb_refcnt);
+ clks[TEGRA30_CLK_DSIA] = clk;
+
+ /* dsib */
+ for (i = 0; i < ARRAY_SIZE(tegra_periph_nodiv_clk_list); i++) {
+ data = &tegra_periph_nodiv_clk_list[i];
+ clk = tegra_clk_register_periph_nodiv(data->name,
+ data->p.parent_names,
+ data->num_parents,
+ &data->periph,
+ clk_base, data->offset);
+ clks[data->clk_id] = clk;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(tegra_periph_clk_list); i++) {
+ data = &tegra_periph_clk_list[i];
+ clk = tegra_clk_register_periph_data(clk_base, data);
+ clks[data->clk_id] = clk;
+ }
+
+ /* pcie */
+ clk = tegra_clk_register_periph_gate("pcie", "clk_m", 0, clk_base, 0,
+ 70, periph_clk_enb_refcnt);
+ clks[TEGRA30_CLK_PCIE] = clk;
+
+ /* afi */
+ clk = tegra_clk_register_periph_gate("afi", "clk_m", 0, clk_base, 0, 72,
+ periph_clk_enb_refcnt);
+ clks[TEGRA30_CLK_AFI] = clk;
+
+ /* emc */
+ clk = tegra20_clk_register_emc(clk_base + CLK_SOURCE_EMC, true);
+
+ clks[TEGRA30_CLK_EMC] = clk;
+
+ tegra_periph_clk_rpm_init(clk_base, tegra30_clks);
+ tegra_init_dup_clks(tegra_clk_duplicates, clks, TEGRA30_CLK_CLK_MAX);
+ tegra_register_devclks(devclks, ARRAY_SIZE(devclks));
+}
+
static void __init tegra30_clock_init(struct device_node *np)
{
struct device_node *node;
@@ -1355,12 +1368,10 @@ static void __init tegra30_clock_init(struct device_node *np)
tegra30_audio_plls,
ARRAY_SIZE(tegra30_audio_plls), 24000000);

- tegra_init_dup_clks(tegra_clk_duplicates, clks, TEGRA30_CLK_CLK_MAX);
-
tegra_add_of_provider(np, tegra30_clk_src_onecell_get);
- tegra_register_devclks(devclks, ARRAY_SIZE(devclks));

tegra_clk_apply_init_table = tegra30_clock_apply_init_table;
+ tegra_clk_apply_rpm_clocks = tegra30_clock_core_rpm_init;

tegra_cpu_car_ops = &tegra30_cpu_car_ops;
}
diff --git a/drivers/clk/tegra/clk.c b/drivers/clk/tegra/clk.c
index f6cdce441cf7..02f3db424376 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;

@@ -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
@@ -372,6 +379,66 @@ struct clk ** __init tegra_lookup_dt_id(int clk_id,
return NULL;
}

+static struct device_node *tegra_clk_dt_node(struct clk_hw *hw)
+{
+ struct device_node *np, *root;
+
+ if (!tegra_car_np)
+ return NULL;
+
+ root = of_get_child_by_name(tegra_car_np, "clocks");
+ if (!root)
+ return NULL;
+
+ for_each_child_of_node(root, np) {
+ if (strcmp(np->name, hw->init->name))
+ continue;
+
+ if (!of_device_is_compatible(np, "nvidia,tegra20-clock") &&
+ !of_device_is_compatible(np, "nvidia,tegra30-clock"))
+ continue;
+
+ return np;
+ }
+
+ of_node_put(root);
+
+ return NULL;
+}
+
+struct clk *tegra_clk_register(struct clk_hw *hw)
+{
+ struct platform_device *pdev;
+ struct device *dev = NULL;
+ struct device_node *np;
+ const char *dev_name;
+
+ np = tegra_clk_dt_node(hw);
+
+ if (!of_device_is_available(np))
+ goto reg_clk;
+
+ dev_name = kasprintf(GFP_KERNEL, "tegra_clk_%s", hw->init->name);
+ if (!dev_name) {
+ of_node_put(np);
+ goto reg_clk;
+ }
+
+ pdev = of_platform_device_create(np, dev_name, NULL);
+ if (!pdev) {
+ pr_err("%s: failed to create device for %pOF\n", __func__, np);
+ kfree(dev_name);
+ of_node_put(np);
+ goto reg_clk;
+ }
+
+ dev = &pdev->dev;
+ pm_runtime_enable(dev);
+
+reg_clk:
+ 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)
@@ -384,3 +451,25 @@ static int __init tegra_clocks_apply_init_table(void)
return 0;
}
arch_initcall(tegra_clocks_apply_init_table);
+
+tegra_clk_apply_rpm_init_table_func tegra_clk_apply_rpm_clocks;
+
+/*
+ * Clocks that use runtime PM can't be created at the CLK_OF_DECLARE
+ * stage 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 drivers base is ready.
+ */
+static int __init tegra_clocks_apply_core_rpm(void)
+{
+ if (!tegra_clk_apply_rpm_clocks)
+ return 0;
+
+ tegra_clk_apply_rpm_clocks();
+
+ return 0;
+}
+postcore_initcall_sync(tegra_clocks_apply_core_rpm);
diff --git a/drivers/clk/tegra/clk.h b/drivers/clk/tegra/clk.h
index c3e36b5dcc75..d45f36237cc0 100644
--- a/drivers/clk/tegra/clk.h
+++ b/drivers/clk/tegra/clk.h
@@ -869,6 +869,9 @@ void tegra_periph_clk_init(void __iomem *clk_base, void __iomem *pmc_base,
struct tegra_clk *tegra_clks,
struct tegra_clk_pll_params *pll_params);

+void tegra_periph_clk_rpm_init(void __iomem *clk_base,
+ struct tegra_clk *tegra_clks);
+
void tegra_fixed_clk_init(struct tegra_clk *tegra_clks);
int tegra_osc_clk_init(void __iomem *clk_base, struct tegra_clk *clks,
unsigned long *input_freqs, unsigned int num,
@@ -907,6 +910,8 @@ void tegra114_clock_deassert_dfll_dvco_reset(void);

typedef void (*tegra_clk_apply_init_table_func)(void);
extern tegra_clk_apply_init_table_func tegra_clk_apply_init_table;
+typedef void (*tegra_clk_apply_rpm_init_table_func)(void);
+extern tegra_clk_apply_rpm_init_table_func tegra_clk_apply_rpm_clocks;
int tegra_pll_wait_for_lock(struct tegra_clk_pll *pll);
u16 tegra_pll_get_fixed_mdiv(struct clk_hw *hw, unsigned long input_rate);
int tegra_pll_p_div_to_hw(struct tegra_clk_pll *pll, u8 p_div);
@@ -931,4 +936,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_register(struct clk_hw *hw);
+
#endif /* TEGRA_CLK_H */
--
2.29.2

2020-12-17 18:14:00

by Dmitry Osipenko

[permalink] [raw]
Subject: [PATCH v2 19/48] opp: Fix adding OPP entries in a wrong order if rate is unavailable

Fix adding OPP entries in a wrong (opposite) order if OPP rate is
unavailable. The OPP comparison is erroneously skipped if OPP rate is
missing, thus OPPs are left unsorted.

Signed-off-by: Dmitry Osipenko <[email protected]>
---
drivers/opp/core.c | 23 ++++++++++++-----------
drivers/opp/opp.h | 2 +-
2 files changed, 13 insertions(+), 12 deletions(-)

diff --git a/drivers/opp/core.c b/drivers/opp/core.c
index 34f7e530d941..5c7f130a8de2 100644
--- a/drivers/opp/core.c
+++ b/drivers/opp/core.c
@@ -1531,9 +1531,10 @@ static bool _opp_supported_by_regulators(struct dev_pm_opp *opp,
return true;
}

-int _opp_compare_key(struct dev_pm_opp *opp1, struct dev_pm_opp *opp2)
+int _opp_compare_key(struct dev_pm_opp *opp1, struct dev_pm_opp *opp2,
+ bool rate_not_available)
{
- if (opp1->rate != opp2->rate)
+ if (!rate_not_available && opp1->rate != opp2->rate)
return opp1->rate < opp2->rate ? -1 : 1;
if (opp1->bandwidth && opp2->bandwidth &&
opp1->bandwidth[0].peak != opp2->bandwidth[0].peak)
@@ -1545,7 +1546,8 @@ int _opp_compare_key(struct dev_pm_opp *opp1, struct dev_pm_opp *opp2)

static int _opp_is_duplicate(struct device *dev, struct dev_pm_opp *new_opp,
struct opp_table *opp_table,
- struct list_head **head)
+ struct list_head **head,
+ bool rate_not_available)
{
struct dev_pm_opp *opp;
int opp_cmp;
@@ -1559,13 +1561,13 @@ static int _opp_is_duplicate(struct device *dev, struct dev_pm_opp *new_opp,
* loop.
*/
list_for_each_entry(opp, &opp_table->opp_list, node) {
- opp_cmp = _opp_compare_key(new_opp, opp);
+ opp_cmp = _opp_compare_key(new_opp, opp, rate_not_available);
if (opp_cmp > 0) {
*head = &opp->node;
continue;
}

- if (opp_cmp < 0)
+ if (opp_cmp < 0 || rate_not_available)
return 0;

/* Duplicate OPPs */
@@ -1601,12 +1603,11 @@ int _opp_add(struct device *dev, struct dev_pm_opp *new_opp,
mutex_lock(&opp_table->lock);
head = &opp_table->opp_list;

- if (likely(!rate_not_available)) {
- ret = _opp_is_duplicate(dev, new_opp, opp_table, &head);
- if (ret) {
- mutex_unlock(&opp_table->lock);
- return ret;
- }
+ ret = _opp_is_duplicate(dev, new_opp, opp_table, &head,
+ rate_not_available);
+ if (ret) {
+ mutex_unlock(&opp_table->lock);
+ return ret;
}

list_add(&new_opp->node, head);
diff --git a/drivers/opp/opp.h b/drivers/opp/opp.h
index 4ced7ffa8158..6f5be6c72f13 100644
--- a/drivers/opp/opp.h
+++ b/drivers/opp/opp.h
@@ -219,7 +219,7 @@ struct opp_table *_find_opp_table(struct device *dev);
struct opp_device *_add_opp_dev(const struct device *dev, struct opp_table *opp_table);
struct dev_pm_opp *_opp_allocate(struct opp_table *opp_table);
void _opp_free(struct dev_pm_opp *opp);
-int _opp_compare_key(struct dev_pm_opp *opp1, struct dev_pm_opp *opp2);
+int _opp_compare_key(struct dev_pm_opp *opp1, struct dev_pm_opp *opp2, bool rate_not_available);
int _opp_add(struct device *dev, struct dev_pm_opp *new_opp, struct opp_table *opp_table, bool rate_not_available);
int _opp_add_v1(struct opp_table *opp_table, struct device *dev, unsigned long freq, long u_volt, bool dynamic);
void _dev_pm_opp_cpumask_remove_table(const struct cpumask *cpumask, int last_cpu);
--
2.29.2

2020-12-17 18:14:11

by Dmitry Osipenko

[permalink] [raw]
Subject: [PATCH v2 33/48] 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 entering into suspend.

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 ce59a6a6a008..565833689be2 100644
--- a/include/linux/host1x.h
+++ b/include/linux/host1x.h
@@ -167,6 +167,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.29.2

2020-12-17 18:14:14

by Dmitry Osipenko

[permalink] [raw]
Subject: [PATCH v2 24/48] soc/tegra: pmc: Ensure that clock rates aren't too high

Switch all clocks of a power domain to a safe rate which is suitable
for all possible voltages in order to ensure that hardware constraints
aren't violated when power domain state toggles.

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

diff --git a/drivers/soc/tegra/pmc.c b/drivers/soc/tegra/pmc.c
index 985373ce52b1..8edcd5b319f6 100644
--- a/drivers/soc/tegra/pmc.c
+++ b/drivers/soc/tegra/pmc.c
@@ -237,6 +237,7 @@ struct tegra_powergate {
unsigned int id;
struct clk **clks;
unsigned int num_clks;
+ unsigned long *clk_rates;
struct reset_control *reset;
};

@@ -586,6 +587,57 @@ static int __tegra_powergate_remove_clamping(struct tegra_pmc *pmc,
return 0;
}

+static int tegra_powergate_prepare_clocks(struct tegra_powergate *pg)
+{
+ unsigned long safe_rate = 100 * 1000 * 1000;
+ unsigned int i;
+ int err;
+
+ for (i = 0; i < pg->num_clks; i++) {
+ pg->clk_rates[i] = clk_get_rate(pg->clks[i]);
+
+ if (!pg->clk_rates[i]) {
+ err = -EINVAL;
+ goto out;
+ }
+
+ if (pg->clk_rates[i] <= safe_rate)
+ continue;
+
+ /*
+ * We don't know whether voltage state is okay for the
+ * current clock rate, hence it's better to temporally
+ * switch clock to a safe rate which is suitable for
+ * all voltages, before enabling the clock.
+ */
+ err = clk_set_rate(pg->clks[i], safe_rate);
+ if (err)
+ goto out;
+ }
+
+ return 0;
+
+out:
+ while (i--)
+ clk_set_rate(pg->clks[i], pg->clk_rates[i]);
+
+ return err;
+}
+
+static int tegra_powergate_unprepare_clocks(struct tegra_powergate *pg)
+{
+ unsigned int i;
+ int err;
+
+ for (i = 0; i < pg->num_clks; i++) {
+ err = clk_set_rate(pg->clks[i], pg->clk_rates[i]);
+ if (err)
+ return err;
+ }
+
+ return 0;
+}
+
static void tegra_powergate_disable_clocks(struct tegra_powergate *pg)
{
unsigned int i;
@@ -636,10 +688,14 @@ static int tegra_powergate_power_up(struct tegra_powergate *pg,

usleep_range(10, 20);

- err = tegra_powergate_enable_clocks(pg);
+ err = tegra_powergate_prepare_clocks(pg);
if (err)
goto powergate_off;

+ err = tegra_powergate_enable_clocks(pg);
+ if (err)
+ goto unprepare_clks;
+
usleep_range(10, 20);

err = __tegra_powergate_remove_clamping(pg->pmc, pg->id);
@@ -670,12 +726,19 @@ static int tegra_powergate_power_up(struct tegra_powergate *pg,
if (disable_clocks)
tegra_powergate_disable_clocks(pg);

+ err = tegra_powergate_unprepare_clocks(pg);
+ if (err)
+ return err;
+
return 0;

disable_clks:
tegra_powergate_disable_clocks(pg);
usleep_range(10, 20);

+unprepare_clks:
+ tegra_powergate_unprepare_clocks(pg);
+
powergate_off:
tegra_powergate_set(pg->pmc, pg->id, false);

@@ -686,10 +749,14 @@ static int tegra_powergate_power_down(struct tegra_powergate *pg)
{
int err;

- err = tegra_powergate_enable_clocks(pg);
+ err = tegra_powergate_prepare_clocks(pg);
if (err)
return err;

+ err = tegra_powergate_enable_clocks(pg);
+ if (err)
+ goto unprepare_clks;
+
usleep_range(10, 20);

err = reset_control_assert(pg->reset);
@@ -706,6 +773,10 @@ static int tegra_powergate_power_down(struct tegra_powergate *pg)
if (err)
goto assert_resets;

+ err = tegra_powergate_unprepare_clocks(pg);
+ if (err)
+ return err;
+
return 0;

assert_resets:
@@ -717,6 +788,9 @@ static int tegra_powergate_power_down(struct tegra_powergate *pg)
disable_clks:
tegra_powergate_disable_clocks(pg);

+unprepare_clks:
+ tegra_powergate_unprepare_clocks(pg);
+
return err;
}

@@ -834,6 +908,12 @@ int tegra_powergate_sequence_power_up(unsigned int id, struct clk *clk,
if (!pg)
return -ENOMEM;

+ pg->clk_rates = kzalloc(sizeof(*pg->clk_rates), GFP_KERNEL);
+ if (!pg->clk_rates) {
+ kfree(pg->clks);
+ return -ENOMEM;
+ }
+
pg->id = id;
pg->clks = &clk;
pg->num_clks = 1;
@@ -845,6 +925,7 @@ int tegra_powergate_sequence_power_up(unsigned int id, struct clk *clk,
dev_err(pmc->dev, "failed to turn on partition %d: %d\n", id,
err);

+ kfree(pg->clk_rates);
kfree(pg);

return err;
@@ -995,6 +1076,12 @@ static int tegra_powergate_of_get_clks(struct tegra_powergate *pg,
if (!pg->clks)
return -ENOMEM;

+ pg->clk_rates = kcalloc(count, sizeof(*pg->clk_rates), GFP_KERNEL);
+ if (!pg->clk_rates) {
+ kfree(pg->clks);
+ return -ENOMEM;
+ }
+
for (i = 0; i < count; i++) {
pg->clks[i] = of_clk_get(np, i);
if (IS_ERR(pg->clks[i])) {
@@ -1011,6 +1098,7 @@ static int tegra_powergate_of_get_clks(struct tegra_powergate *pg,
while (i--)
clk_put(pg->clks[i]);

+ kfree(pg->clk_rates);
kfree(pg->clks);

return err;
--
2.29.2

2020-12-17 18:14:20

by Dmitry Osipenko

[permalink] [raw]
Subject: [PATCH v2 25/48] soc/tegra: pmc: Print out domain name when reset fails to acquire

Print out domain name when reset fails to acquire for debugging purposes
and to make formatting of GENPD errors consistent in the driver.

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

diff --git a/drivers/soc/tegra/pmc.c b/drivers/soc/tegra/pmc.c
index 8edcd5b319f6..4f96dc7745c4 100644
--- a/drivers/soc/tegra/pmc.c
+++ b/drivers/soc/tegra/pmc.c
@@ -821,7 +821,8 @@ static int tegra_genpd_power_off(struct generic_pm_domain *domain)

err = reset_control_acquire(pg->reset);
if (err < 0) {
- pr_err("failed to acquire resets: %d\n", err);
+ dev_err(dev, "failed to acquire resets for PM domain %s: %d\n",
+ pg->genpd.name, err);
return err;
}

--
2.29.2

2020-12-17 18:14:40

by Dmitry Osipenko

[permalink] [raw]
Subject: [PATCH v2 20/48] PM: domains: Make set_performance_state() callback optional

Make set_performance_state() callback optional in order to remove the
need from power domain drivers to implement a dummy callback. If callback
isn't implemented by a GENPD driver, then the performance state is passed
to the parent domain.

Signed-off-by: Dmitry Osipenko <[email protected]>
---
drivers/base/power/domain.c | 11 +++++------
1 file changed, 5 insertions(+), 6 deletions(-)

diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c
index 9a14eedacb92..a3e1bfc233d4 100644
--- a/drivers/base/power/domain.c
+++ b/drivers/base/power/domain.c
@@ -339,9 +339,11 @@ static int _genpd_set_performance_state(struct generic_pm_domain *genpd,
goto err;
}

- ret = genpd->set_performance_state(genpd, state);
- if (ret)
- goto err;
+ if (genpd->set_performance_state) {
+ ret = genpd->set_performance_state(genpd, state);
+ if (ret)
+ goto err;
+ }

genpd->performance_state = state;
return 0;
@@ -399,9 +401,6 @@ int dev_pm_genpd_set_performance_state(struct device *dev, unsigned int state)
if (!genpd)
return -ENODEV;

- if (unlikely(!genpd->set_performance_state))
- return -EINVAL;
-
if (WARN_ON(!dev->power.subsys_data ||
!dev->power.subsys_data->domain_data))
return -EINVAL;
--
2.29.2

2020-12-17 18:14:43

by Dmitry Osipenko

[permalink] [raw]
Subject: [PATCH v2 42/48] memory: tegra30-emc: Use devm_tegra_core_dev_init_opp_table()

Use common devm_tegra_core_dev_init_opp_table() helper for the OPP table
initialization.

Signed-off-by: Dmitry Osipenko <[email protected]>
---
drivers/memory/tegra/tegra30-emc.c | 57 +++---------------------------
1 file changed, 4 insertions(+), 53 deletions(-)

diff --git a/drivers/memory/tegra/tegra30-emc.c b/drivers/memory/tegra/tegra30-emc.c
index 44ac155936aa..c6309afd9939 100644
--- a/drivers/memory/tegra/tegra30-emc.c
+++ b/drivers/memory/tegra/tegra30-emc.c
@@ -1480,58 +1480,6 @@ static int tegra_emc_interconnect_init(struct tegra_emc *emc)
return err;
}

-static int tegra_emc_opp_table_init(struct tegra_emc *emc)
-{
- u32 hw_version = BIT(tegra_sku_info.soc_speedo_id);
- struct opp_table *clk_opp_table, *hw_opp_table;
- int err;
-
- clk_opp_table = dev_pm_opp_set_clkname(emc->dev, NULL);
- err = PTR_ERR_OR_ZERO(clk_opp_table);
- if (err) {
- dev_err(emc->dev, "failed to set OPP clk: %d\n", err);
- return err;
- }
-
- hw_opp_table = dev_pm_opp_set_supported_hw(emc->dev, &hw_version, 1);
- err = PTR_ERR_OR_ZERO(hw_opp_table);
- if (err) {
- dev_err(emc->dev, "failed to set OPP supported HW: %d\n", err);
- goto put_clk_table;
- }
-
- err = dev_pm_opp_of_add_table(emc->dev);
- if (err) {
- if (err == -ENODEV)
- dev_err(emc->dev, "OPP table not found, please update your device tree\n");
- else
- dev_err(emc->dev, "failed to add OPP table: %d\n", err);
-
- goto put_hw_table;
- }
-
- dev_info(emc->dev, "OPP HW ver. 0x%x, current clock rate %lu MHz\n",
- hw_version, clk_get_rate(emc->clk) / 1000000);
-
- /* first dummy rate-set initializes voltage state */
- err = dev_pm_opp_set_rate(emc->dev, clk_get_rate(emc->clk));
- if (err) {
- dev_err(emc->dev, "failed to initialize OPP clock: %d\n", err);
- goto remove_table;
- }
-
- return 0;
-
-remove_table:
- dev_pm_opp_of_remove_table(emc->dev);
-put_hw_table:
- dev_pm_opp_put_supported_hw(hw_opp_table);
-put_clk_table:
- dev_pm_opp_put_clkname(clk_opp_table);
-
- return err;
-}
-
static void devm_tegra_emc_unset_callback(void *data)
{
tegra20_clk_set_emc_round_callback(NULL, NULL);
@@ -1577,6 +1525,7 @@ static int tegra_emc_init_clk(struct tegra_emc *emc)

static int tegra_emc_probe(struct platform_device *pdev)
{
+ struct tegra_core_opp_params opp_params = {};
struct device_node *np;
struct tegra_emc *emc;
int err;
@@ -1626,7 +1575,9 @@ static int tegra_emc_probe(struct platform_device *pdev)
if (err)
return err;

- err = tegra_emc_opp_table_init(emc);
+ opp_params.init_state = true;
+
+ err = devm_tegra_core_dev_init_opp_table(&pdev->dev, &opp_params);
if (err)
return err;

--
2.29.2

2020-12-17 18:14:47

by Dmitry Osipenko

[permalink] [raw]
Subject: [PATCH v2 14/48] opp: Filter out OPPs based on availability of a required-OPP

A required OPP may not be available, and thus, all OPPs which are using
this required OPP should be unavailable too.

Signed-off-by: Dmitry Osipenko <[email protected]>
---
drivers/opp/core.c | 11 ++++++++++-
1 file changed, 10 insertions(+), 1 deletion(-)

diff --git a/drivers/opp/core.c b/drivers/opp/core.c
index d9feb7639598..3d02fe33630b 100644
--- a/drivers/opp/core.c
+++ b/drivers/opp/core.c
@@ -1588,7 +1588,7 @@ int _opp_add(struct device *dev, struct dev_pm_opp *new_opp,
struct opp_table *opp_table, bool rate_not_available)
{
struct list_head *head;
- int ret;
+ int i, ret;

mutex_lock(&opp_table->lock);
head = &opp_table->opp_list;
@@ -1615,6 +1615,15 @@ int _opp_add(struct device *dev, struct dev_pm_opp *new_opp,
__func__, new_opp->rate);
}

+ for (i = 0; i < opp_table->required_opp_count && new_opp->available; i++) {
+ if (new_opp->required_opps[i]->available)
+ continue;
+
+ new_opp->available = false;
+ dev_warn(dev, "%s: OPP not supported by required OPP %pOF (%lu)\n",
+ __func__, new_opp->required_opps[i]->np, new_opp->rate);
+ }
+
return 0;
}

--
2.29.2

2020-12-17 18:14:55

by Dmitry Osipenko

[permalink] [raw]
Subject: [PATCH v2 12/48] opp: Add dev_pm_opp_get_required_pstate()

Add dev_pm_opp_get_required_pstate() which allows OPP users to retrieve
required performance state of a given OPP.

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

diff --git a/drivers/opp/core.c b/drivers/opp/core.c
index 0783a4ac819a..4774701ec82d 100644
--- a/drivers/opp/core.c
+++ b/drivers/opp/core.c
@@ -145,6 +145,28 @@ unsigned int dev_pm_opp_get_level(struct dev_pm_opp *opp)
}
EXPORT_SYMBOL_GPL(dev_pm_opp_get_level);

+/**
+ * dev_pm_opp_get_required_pstate() - Gets the required performance state
+ * corresponding to an available opp
+ * @opp: opp for which performance state has to be returned for
+ * @index: index of the required opp
+ *
+ * Return: performance state read from device tree corresponding to the
+ * required opp, else return 0.
+ */
+unsigned int dev_pm_opp_get_required_pstate(struct dev_pm_opp *opp,
+ unsigned int index)
+{
+ if (IS_ERR_OR_NULL(opp) || !opp->available ||
+ index >= opp->opp_table->required_opp_count) {
+ pr_err("%s: Invalid parameters\n", __func__);
+ return 0;
+ }
+
+ return opp->required_opps[index]->pstate;
+}
+EXPORT_SYMBOL_GPL(dev_pm_opp_get_required_pstate);
+
/**
* dev_pm_opp_is_turbo() - Returns if opp is turbo OPP or not
* @opp: opp for which turbo mode is being verified
diff --git a/include/linux/pm_opp.h b/include/linux/pm_opp.h
index a17d92d923cc..0298b426fba3 100644
--- a/include/linux/pm_opp.h
+++ b/include/linux/pm_opp.h
@@ -98,6 +98,9 @@ unsigned long dev_pm_opp_get_freq(struct dev_pm_opp *opp);

unsigned int dev_pm_opp_get_level(struct dev_pm_opp *opp);

+unsigned int dev_pm_opp_get_required_pstate(struct dev_pm_opp *opp,
+ unsigned int index);
+
bool dev_pm_opp_is_turbo(struct dev_pm_opp *opp);

int dev_pm_opp_get_opp_count(struct device *dev);
@@ -188,6 +191,13 @@ static inline unsigned int dev_pm_opp_get_level(struct dev_pm_opp *opp)
return 0;
}

+static inline
+unsigned int dev_pm_opp_get_required_pstate(struct dev_pm_opp *opp,
+ unsigned int index)
+{
+ return 0;
+}
+
static inline bool dev_pm_opp_is_turbo(struct dev_pm_opp *opp)
{
return false;
--
2.29.2

2020-12-17 18:15:04

by Dmitry Osipenko

[permalink] [raw]
Subject: [PATCH v2 38/48] drm/tegra: g3d: Support OPP and power management

Add OPP and add PM support to the GR3D driver. This is required for
enabling system-wide DVFS and supporting dynamic power management using
a generic power domain.

Tested-by: Peter Geis <[email protected]>
Tested-by: Nicolas Chauvet <[email protected]>
Signed-off-by: Dmitry Osipenko <[email protected]>
---
drivers/gpu/drm/tegra/gr3d.c | 264 +++++++++++++++++++++++++++++++----
1 file changed, 238 insertions(+), 26 deletions(-)

diff --git a/drivers/gpu/drm/tegra/gr3d.c b/drivers/gpu/drm/tegra/gr3d.c
index b0b8154e8104..11c38af584ee 100644
--- a/drivers/gpu/drm/tegra/gr3d.c
+++ b/drivers/gpu/drm/tegra/gr3d.c
@@ -10,8 +10,12 @@
#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"
@@ -31,6 +35,9 @@ struct gr3d {
struct reset_control *rst;

const struct gr3d_soc *soc;
+ struct clk_bulk_data clocks[2];
+ unsigned int nclocks;
+ bool legacy_pd;

DECLARE_BITMAP(addr_regs, GR3D_NUM_REGS);
};
@@ -278,10 +285,120 @@ static const u32 gr3d_addr_regs[] = {
GR3D_GLOBAL_SAMP23SURFADDR(15),
};

+static void gr3d_pm_runtime_release(void *dev)
+{
+ pm_runtime_put(dev);
+ pm_runtime_disable(dev);
+}
+
+static int gr3d_link_power_domain(struct device *dev, struct device *pd_dev)
+{
+ const u32 link_flags = DL_FLAG_STATELESS | DL_FLAG_PM_RUNTIME;
+ struct device_link *link;
+ int err;
+
+ 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, (void *)device_link_del, link);
+ if (err)
+ return err;
+
+ return 0;
+}
+
+static int devm_gr3d_init_power(struct device *dev, struct gr3d *gr3d)
+{
+ const char *opp_genpd_names[] = { "3d0", "3d1", NULL };
+ struct device **opp_virt_dev;
+ struct opp_table *opp_table;
+ unsigned int i, num_domains;
+ struct device *pd_dev;
+ 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.
+ */
+ gr3d->legacy_pd = true;
+ goto power_up;
+ }
+
+ num_domains = err;
+
+ /*
+ * 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)
+ goto power_up;
+
+ opp_table = devm_pm_opp_attach_genpd(dev, opp_genpd_names, &opp_virt_dev);
+ if (IS_ERR(opp_table))
+ return PTR_ERR(opp_table);
+
+ for (i = 0; opp_genpd_names[i]; i++) {
+ pd_dev = opp_virt_dev[i];
+ if (!pd_dev) {
+ dev_err(dev, "failed to get %s power domain\n",
+ opp_genpd_names[i]);
+ return -EINVAL;
+ }
+
+ err = gr3d_link_power_domain(dev, pd_dev);
+ if (err)
+ return err;
+ }
+
+power_up:
+ pm_runtime_enable(dev);
+ err = pm_runtime_get_sync(dev);
+ if (err < 0) {
+ gr3d_pm_runtime_release(dev);
+ return err;
+ }
+
+ err = devm_add_action_or_reset(dev, gr3d_pm_runtime_release, dev);
+ if (err)
+ return err;
+
+ 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);
+ return err;
+ }
+ }
+
+ return 0;
+}
+
static int gr3d_probe(struct platform_device *pdev)
{
+ struct tegra_core_opp_params opp_params = {};
struct device_node *np = pdev->dev.of_node;
struct host1x_syncpt **syncpts;
+ struct opp_table *opp_table;
struct gr3d *gr3d;
unsigned int i;
int err;
@@ -290,6 +407,8 @@ static int gr3d_probe(struct platform_device *pdev)
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);
@@ -302,7 +421,11 @@ static int gr3d_probe(struct platform_device *pdev)
return PTR_ERR(gr3d->clk);
}

- gr3d->rst = devm_reset_control_get(&pdev->dev, "3d");
+ gr3d->clocks[gr3d->nclocks].id = "3d";
+ gr3d->clocks[gr3d->nclocks].clk = gr3d->clk;
+ gr3d->nclocks++;
+
+ gr3d->rst = devm_reset_control_get_exclusive_released(&pdev->dev, "3d");
if (IS_ERR(gr3d->rst)) {
dev_err(&pdev->dev, "cannot get reset\n");
return PTR_ERR(gr3d->rst);
@@ -315,31 +438,31 @@ static int gr3d_probe(struct platform_device *pdev)
return PTR_ERR(gr3d->clk_secondary);
}

- gr3d->rst_secondary = devm_reset_control_get(&pdev->dev,
- "3d2");
+ gr3d->clocks[gr3d->nclocks].id = "3d2";
+ gr3d->clocks[gr3d->nclocks].clk = gr3d->clk_secondary;
+ gr3d->nclocks++;
+
+ gr3d->rst_secondary =
+ devm_reset_control_get_exclusive_released(&pdev->dev, "3d2");
if (IS_ERR(gr3d->rst_secondary)) {
dev_err(&pdev->dev, "cannot get secondary reset\n");
return PTR_ERR(gr3d->rst_secondary);
}
}

- err = tegra_powergate_sequence_power_up(TEGRA_POWERGATE_3D, gr3d->clk,
- gr3d->rst);
- if (err < 0) {
- dev_err(&pdev->dev, "failed to power up 3D unit\n");
+ err = devm_gr3d_init_power(&pdev->dev, gr3d);
+ if (err)
return 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;
- }
- }
+ opp_table = devm_pm_opp_register_set_opp_helper(&pdev->dev, gr3d_set_opp);
+ if (IS_ERR(opp_table))
+ return PTR_ERR(opp_table);
+
+ opp_params.init_state = true;
+
+ err = devm_tegra_core_dev_init_opp_table(&pdev->dev, &opp_params);
+ if (err && err != -ENODEV)
+ return err;

INIT_LIST_HEAD(&gr3d->client.base.list);
gr3d->client.base.ops = &gr3d_client_ops;
@@ -363,8 +486,6 @@ static int gr3d_probe(struct platform_device *pdev)
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;
}

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

- if (gr3d->clk_secondary) {
- reset_control_assert(gr3d->rst_secondary);
+ return 0;
+}
+
+static int __maybe_unused gr3d_runtime_suspend(struct device *dev)
+{
+ struct gr3d *gr3d = dev_get_drvdata(dev);
+ int err;
+
+ if (gr3d->legacy_pd && gr3d->clk_secondary) {
+ err = reset_control_assert(gr3d->rst_secondary);
+ if (err) {
+ dev_err(dev, "failed to assert secondary reset: %d\n", err);
+ return err;
+ }
+
tegra_powergate_power_off(TEGRA_POWERGATE_3D1);
- clk_disable_unprepare(gr3d->clk_secondary);
}

- reset_control_assert(gr3d->rst);
- tegra_powergate_power_off(TEGRA_POWERGATE_3D);
- clk_disable_unprepare(gr3d->clk);
+ if (gr3d->legacy_pd) {
+ err = reset_control_assert(gr3d->rst);
+ if (err) {
+ dev_err(dev, "failed to assert reset: %d\n", err);
+ return err;
+ }
+
+ tegra_powergate_power_off(TEGRA_POWERGATE_3D);
+ }
+
+ clk_bulk_disable_unprepare(gr3d->nclocks, gr3d->clocks);
+ reset_control_release(gr3d->rst_secondary);
+ reset_control_release(gr3d->rst);
+
+ return 0;
+}
+
+static int __maybe_unused gr3d_runtime_resume(struct device *dev)
+{
+ struct gr3d *gr3d = dev_get_drvdata(dev);
+ int err;
+
+ err = reset_control_acquire(gr3d->rst);
+ if (err) {
+ dev_err(dev, "failed to acquire reset: %d\n", err);
+ return err;
+ }
+
+ err = reset_control_acquire(gr3d->rst_secondary);
+ if (err) {
+ dev_err(dev, "failed to acquire secondary reset: %d\n", err);
+ goto release_reset_primary;
+ }
+
+ if (gr3d->legacy_pd) {
+ err = tegra_powergate_sequence_power_up(TEGRA_POWERGATE_3D,
+ gr3d->clk, gr3d->rst);
+ if (err)
+ goto release_reset_secondary;
+ }
+
+ if (gr3d->legacy_pd && gr3d->clk_secondary) {
+ err = tegra_powergate_sequence_power_up(TEGRA_POWERGATE_3D1,
+ gr3d->clk_secondary,
+ gr3d->rst_secondary);
+ if (err)
+ goto release_reset_secondary;
+ }
+
+ err = clk_bulk_prepare_enable(gr3d->nclocks, gr3d->clocks);
+ if (err) {
+ dev_err(dev, "failed to enable clock: %d\n", err);
+ goto release_reset_secondary;
+ }
+
+ return 0;
+
+release_reset_secondary:
+ reset_control_release(gr3d->rst_secondary);
+
+release_reset_primary:
+ reset_control_release(gr3d->rst);
+
+ return err;
+}
+
+static __maybe_unused int gr3d_suspend(struct device *dev)
+{
+ struct gr3d *gr3d = dev_get_drvdata(dev);
+ int err;
+
+ host1x_channel_stop(gr3d->channel);
+
+ err = pm_runtime_force_suspend(dev);
+ if (err < 0)
+ return err;

return 0;
}

+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(gr3d_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.29.2

2020-12-17 18:15:26

by Dmitry Osipenko

[permalink] [raw]
Subject: [PATCH v2 13/48] opp: Add resource-managed versions of OPP API functions

Add resource-managed versions of OPP API functions. This removes a need
from drivers to store and manage OPP table pointers.

Signed-off-by: Dmitry Osipenko <[email protected]>
---
drivers/opp/core.c | 173 +++++++++++++++++++++++++++++++++++++++++
drivers/opp/of.c | 25 ++++++
include/linux/pm_opp.h | 51 ++++++++++++
3 files changed, 249 insertions(+)

diff --git a/drivers/opp/core.c b/drivers/opp/core.c
index 4774701ec82d..d9feb7639598 100644
--- a/drivers/opp/core.c
+++ b/drivers/opp/core.c
@@ -2664,3 +2664,176 @@ int dev_pm_opp_set_voltage(struct device *dev, struct dev_pm_opp *opp)
return ret;
}
EXPORT_SYMBOL_GPL(dev_pm_opp_set_voltage);
+
+/**
+ * devm_pm_opp_get_opp_table() - Get OPP table of a device
+ * @dev: device for which we do this operation
+ *
+ * This is a resource-managed version of dev_pm_opp_get_opp_table().
+ *
+ * Return: pointer to 'struct opp_table' if found and -ENODEV otherwise.
+ */
+struct opp_table *devm_pm_opp_get_opp_table(struct device *dev)
+{
+ struct opp_table *opp_table;
+ int err;
+
+ opp_table = dev_pm_opp_get_opp_table(dev);
+ if (IS_ERR(opp_table))
+ return opp_table;
+
+ err = devm_add_action_or_reset(dev, (void *)dev_pm_opp_put_opp_table,
+ opp_table);
+ if (err)
+ return ERR_PTR(err);
+
+ return opp_table;
+}
+EXPORT_SYMBOL_GPL(devm_pm_opp_get_opp_table);
+
+/**
+ * devm_pm_opp_set_regulators() - Set regulator names for the device
+ * @dev: Device for which regulator name is being set.
+ * @names: Array of pointers to the names of the regulator.
+ * @count: Number of regulators.
+ *
+ * This is a resource-managed version of dev_pm_opp_set_regulators().
+ *
+ * Return: pointer to 'struct opp_table' on success and errorno otherwise.
+ */
+struct opp_table *
+devm_pm_opp_set_regulators(struct device *dev, const char * const names[],
+ unsigned int count)
+{
+ struct opp_table *opp_table;
+ int err;
+
+ opp_table = dev_pm_opp_set_regulators(dev, names, count);
+ if (IS_ERR(opp_table))
+ return opp_table;
+
+ err = devm_add_action_or_reset(dev, (void *)dev_pm_opp_put_regulators,
+ opp_table);
+ if (err)
+ return ERR_PTR(err);
+
+ return opp_table;
+}
+EXPORT_SYMBOL_GPL(devm_pm_opp_set_regulators);
+
+/**
+ * devm_pm_opp_set_supported_hw() - Set supported platforms
+ * @dev: Device for which supported-hw has to be set.
+ * @versions: Array of hierarchy of versions to match.
+ * @count: Number of elements in the array.
+ *
+ * This is a resource-managed version of dev_pm_opp_set_supported_hw().
+ *
+ * Return: pointer to 'struct opp_table' on success and errorno otherwise.
+ */
+struct opp_table *
+devm_pm_opp_set_supported_hw(struct device *dev, const u32 *versions,
+ unsigned int count)
+{
+ struct opp_table *opp_table;
+ int err;
+
+ opp_table = dev_pm_opp_set_supported_hw(dev, versions, count);
+ if (IS_ERR(opp_table))
+ return opp_table;
+
+ err = devm_add_action_or_reset(dev, (void *)dev_pm_opp_put_supported_hw,
+ opp_table);
+ if (err)
+ return ERR_PTR(err);
+
+ return opp_table;
+}
+EXPORT_SYMBOL_GPL(devm_pm_opp_set_supported_hw);
+
+/**
+ * devm_pm_opp_set_clkname() - Set clk name for the device
+ * @dev: Device for which clk name is being set.
+ * @name: Clk name.
+ *
+ * This is a resource-managed version of dev_pm_opp_set_clkname().
+ *
+ * Return: pointer to 'struct opp_table' on success and errorno otherwise.
+ */
+struct opp_table *
+devm_pm_opp_set_clkname(struct device *dev, const char *name)
+{
+ struct opp_table *opp_table;
+ int err;
+
+ opp_table = dev_pm_opp_set_clkname(dev, name);
+ if (IS_ERR(opp_table))
+ return opp_table;
+
+ err = devm_add_action_or_reset(dev, (void *)dev_pm_opp_put_clkname,
+ opp_table);
+ if (err)
+ return ERR_PTR(err);
+
+ return opp_table;
+}
+EXPORT_SYMBOL_GPL(devm_pm_opp_set_clkname);
+
+/**
+ * devm_pm_opp_register_set_opp_helper() - Register custom set OPP helper
+ * @dev: Device for which the helper is getting registered.
+ * @set_opp: Custom set OPP helper.
+ *
+ * This is a resource-managed version of dev_pm_opp_register_set_opp_helper().
+ *
+ * Return: pointer to 'struct opp_table' on success and errorno otherwise.
+ */
+struct opp_table *
+devm_pm_opp_register_set_opp_helper(struct device *dev,
+ int (*set_opp)(struct dev_pm_set_opp_data *data))
+{
+ struct opp_table *opp_table;
+ int err;
+
+ opp_table = dev_pm_opp_register_set_opp_helper(dev, set_opp);
+ if (IS_ERR(opp_table))
+ return opp_table;
+
+ err = devm_add_action_or_reset(dev, (void *)dev_pm_opp_unregister_set_opp_helper,
+ opp_table);
+ if (err)
+ return ERR_PTR(err);
+
+ return opp_table;
+}
+EXPORT_SYMBOL_GPL(devm_pm_opp_register_set_opp_helper);
+
+/**
+ * devm_pm_opp_attach_genpd - Attach genpd(s) for the device and save virtual device pointer
+ * @dev: Consumer device for which the genpd is getting attached.
+ * @names: Null terminated array of pointers containing names of genpd to attach.
+ * @virt_devs: Pointer to return the array of virtual devices.
+ *
+ * This is a resource-managed version of dev_pm_opp_attach_genpd().
+ *
+ * Return: pointer to 'struct opp_table' on success and errorno otherwise.
+ */
+struct opp_table *
+devm_pm_opp_attach_genpd(struct device *dev, const char **names,
+ struct device ***virt_devs)
+{
+ struct opp_table *opp_table;
+ int err;
+
+ opp_table = dev_pm_opp_attach_genpd(dev, names, virt_devs);
+ if (IS_ERR(opp_table))
+ return opp_table;
+
+ err = devm_add_action_or_reset(dev, (void *)dev_pm_opp_detach_genpd,
+ opp_table);
+ if (err)
+ return ERR_PTR(err);
+
+ return opp_table;
+}
+EXPORT_SYMBOL_GPL(devm_pm_opp_attach_genpd);
diff --git a/drivers/opp/of.c b/drivers/opp/of.c
index 03cb387236c4..3b5a4c8bc62f 100644
--- a/drivers/opp/of.c
+++ b/drivers/opp/of.c
@@ -1350,3 +1350,28 @@ int dev_pm_opp_of_register_em(struct device *dev, struct cpumask *cpus)
return ret;
}
EXPORT_SYMBOL_GPL(dev_pm_opp_of_register_em);
+
+/**
+ * devm_pm_opp_of_add_table() - Initialize opp table from device tree
+ * @dev: device pointer used to lookup OPP table.
+ *
+ * This is a resource-managed version of dev_pm_opp_of_add_table().
+ *
+ * Return: 0 on success and errorno otherwise.
+ */
+int devm_pm_opp_of_add_table(struct device *dev)
+{
+ int err;
+
+ err = dev_pm_opp_of_add_table(dev);
+ if (err)
+ return err;
+
+ err = devm_add_action_or_reset(dev, (void *)dev_pm_opp_remove_table,
+ dev);
+ if (err)
+ return err;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(devm_pm_opp_of_add_table);
diff --git a/include/linux/pm_opp.h b/include/linux/pm_opp.h
index 0298b426fba3..0d42fbda0134 100644
--- a/include/linux/pm_opp.h
+++ b/include/linux/pm_opp.h
@@ -163,6 +163,12 @@ void dev_pm_opp_remove_table(struct device *dev);
void dev_pm_opp_cpumask_remove_table(const struct cpumask *cpumask);
int dev_pm_opp_sync_regulators(struct device *dev);
int dev_pm_opp_set_voltage(struct device *dev, struct dev_pm_opp *opp);
+struct opp_table *devm_pm_opp_get_opp_table(struct device *dev);
+struct opp_table *devm_pm_opp_set_regulators(struct device *dev, const char * const names[], unsigned int count);
+struct opp_table *devm_pm_opp_set_supported_hw(struct device *dev, const u32 *versions, unsigned int count);
+struct opp_table *devm_pm_opp_set_clkname(struct device *dev, const char *name);
+struct opp_table *devm_pm_opp_register_set_opp_helper(struct device *dev, int (*set_opp)(struct dev_pm_set_opp_data *data));
+struct opp_table *devm_pm_opp_attach_genpd(struct device *dev, const char **names, struct device ***virt_devs);
#else
static inline struct opp_table *dev_pm_opp_get_opp_table(struct device *dev)
{
@@ -396,9 +402,49 @@ static inline int dev_pm_opp_set_voltage(struct device *dev, struct dev_pm_opp *
return -ENOTSUPP;
}

+static inline struct opp_table *devm_pm_opp_get_opp_table(struct device *dev)
+{
+ return ERR_PTR(-ENOTSUPP);
+}
+
+static inline struct opp_table *
+devm_pm_opp_set_regulators(struct device *dev, const char * const names[],
+ unsigned int count)
+{
+ return ERR_PTR(-ENOTSUPP);
+}
+
+static inline struct opp_table *
+devm_pm_opp_set_supported_hw(struct device *dev, const u32 *versions,
+ unsigned int count)
+{
+ return ERR_PTR(-ENOTSUPP);
+}
+
+static inline struct opp_table *
+devm_pm_opp_set_clkname(struct device *dev, const char *name)
+{
+ return ERR_PTR(-ENOTSUPP);
+}
+
+static inline struct opp_table *
+devm_pm_opp_register_set_opp_helper(struct device *dev,
+ int (*set_opp)(struct dev_pm_set_opp_data *data))
+{
+ return ERR_PTR(-ENOTSUPP);
+}
+
+static inline struct opp_table *
+devm_pm_opp_attach_genpd(struct device *dev, const char **names,
+ struct device ***virt_devs)
+{
+ return ERR_PTR(-ENOTSUPP);
+}
+
#endif /* CONFIG_PM_OPP */

#if defined(CONFIG_PM_OPP) && defined(CONFIG_OF)
+int devm_pm_opp_of_add_table(struct device *dev);
int dev_pm_opp_of_add_table(struct device *dev);
int dev_pm_opp_of_add_table_indexed(struct device *dev, int index);
void dev_pm_opp_of_remove_table(struct device *dev);
@@ -472,6 +518,11 @@ static inline int dev_pm_opp_of_find_icc_paths(struct device *dev, struct opp_ta
{
return -ENOTSUPP;
}
+
+static inline int devm_pm_opp_of_add_table(struct device *dev)
+{
+ return -ENOTSUPP;
+}
#endif

#endif /* __LINUX_OPP_H__ */
--
2.29.2

2020-12-17 18:15:31

by Dmitry Osipenko

[permalink] [raw]
Subject: [PATCH v2 07/48] dt-bindings: arm: tegra: Add binding for core power domain

All NVIDIA Tegra SoCs have a core power domain where majority of hardware
blocks reside. Add binding for the core power domain.

Signed-off-by: Dmitry Osipenko <[email protected]>
---
.../arm/tegra/nvidia,tegra20-core-domain.yaml | 48 +++++++++++++++++++
1 file changed, 48 insertions(+)
create mode 100644 Documentation/devicetree/bindings/arm/tegra/nvidia,tegra20-core-domain.yaml

diff --git a/Documentation/devicetree/bindings/arm/tegra/nvidia,tegra20-core-domain.yaml b/Documentation/devicetree/bindings/arm/tegra/nvidia,tegra20-core-domain.yaml
new file mode 100644
index 000000000000..f3d8fd2d8371
--- /dev/null
+++ b/Documentation/devicetree/bindings/arm/tegra/nvidia,tegra20-core-domain.yaml
@@ -0,0 +1,48 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/arm/tegra/nvidia,tegra20-core-domain.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: NVIDIA Tegra Core Power Domain
+
+maintainers:
+ - Dmitry Osipenko <[email protected]>
+ - Jon Hunter <[email protected]>
+ - Thierry Reding <[email protected]>
+
+properties:
+ compatible:
+ enum:
+ - nvidia,tegra20-core-domain
+ - nvidia,tegra30-core-domain
+
+ operating-points-v2:
+ description:
+ Should contain level, voltages and opp-supported-hw property.
+ The supported-hw is a bitfield indicating SoC speedo or process
+ ID mask.
+
+ "#power-domain-cells":
+ const: 0
+
+ power-supply:
+ description:
+ Phandle to voltage regulator connected to the SoC Core power rail.
+
+required:
+ - compatible
+ - operating-points-v2
+ - "#power-domain-cells"
+ - power-supply
+
+additionalProperties: false
+
+examples:
+ - |
+ core-domain {
+ compatible = "nvidia,tegra20-core-domain";
+ operating-points-v2 = <&opp_table>;
+ power-supply = <&regulator>;
+ #power-domain-cells = <0>;
+ };
--
2.29.2

2020-12-17 18:15:52

by Dmitry Osipenko

[permalink] [raw]
Subject: [PATCH v2 10/48] opp: Add dev_pm_opp_set_voltage()

Add dev_pm_opp_set_voltage() which allows OPP table users to set voltage
in accordance to a given OPP. In particular this is needed for driving
voltage of a generic power domain which uses OPPs and doesn't have a
clock.

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

diff --git a/drivers/opp/core.c b/drivers/opp/core.c
index 500d6c716283..eab37b3a27bb 100644
--- a/drivers/opp/core.c
+++ b/drivers/opp/core.c
@@ -2541,3 +2541,55 @@ int dev_pm_opp_sync_regulators(struct device *dev)
return ret;
}
EXPORT_SYMBOL_GPL(dev_pm_opp_sync_regulators);
+
+/**
+ * dev_pm_opp_set_voltage() - Change voltage of regulators
+ * @dev: device for which we do this operation
+ * @opp: opp based on which the voltages are to be configured
+ *
+ * Change voltage of the OPP table regulators.
+ *
+ * Return: 0 on success or a negative error value.
+ */
+int dev_pm_opp_set_voltage(struct device *dev, struct dev_pm_opp *opp)
+{
+ struct opp_table *opp_table;
+ struct regulator *reg;
+ int ret = 0;
+
+ /* Device may not have OPP table */
+ opp_table = _find_opp_table(dev);
+ if (IS_ERR(opp_table))
+ return 0;
+
+ /* Regulator may not be required for the device */
+ if (!opp_table->regulators)
+ goto put_table;
+
+ /* This function only supports single regulator per device */
+ if (WARN_ON(opp_table->regulator_count > 1)) {
+ dev_err(dev, "multiple regulators are not supported\n");
+ ret = -EINVAL;
+ goto put_table;
+ }
+
+ reg = opp_table->regulators[0];
+ ret = _set_opp_voltage(dev, reg, opp->supplies);
+
+ if (!opp_table->enabled) {
+ ret = regulator_enable(reg);
+ if (ret < 0) {
+ dev_warn(dev, "Failed to enable regulator: %d", ret);
+ goto put_table;
+ }
+
+ opp_table->enabled = true;
+ }
+
+put_table:
+ /* Drop reference taken by _find_opp_table() */
+ dev_pm_opp_put_opp_table(opp_table);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(dev_pm_opp_set_voltage);
diff --git a/include/linux/pm_opp.h b/include/linux/pm_opp.h
index 4c79faa2025e..f311a8b2ca04 100644
--- a/include/linux/pm_opp.h
+++ b/include/linux/pm_opp.h
@@ -157,6 +157,7 @@ int dev_pm_opp_get_sharing_cpus(struct device *cpu_dev, struct cpumask *cpumask)
void dev_pm_opp_remove_table(struct device *dev);
void dev_pm_opp_cpumask_remove_table(const struct cpumask *cpumask);
int dev_pm_opp_sync_regulators(struct device *dev);
+int dev_pm_opp_set_voltage(struct device *dev, struct dev_pm_opp *opp);
#else
static inline struct opp_table *dev_pm_opp_get_opp_table(struct device *dev)
{
@@ -372,6 +373,11 @@ static inline int dev_pm_opp_sync_regulators(struct device *dev)
return -ENOTSUPP;
}

+static inline int dev_pm_opp_set_voltage(struct device *dev, struct dev_pm_opp *opp)
+{
+ return -ENOTSUPP;
+}
+
#endif /* CONFIG_PM_OPP */

#if defined(CONFIG_PM_OPP) && defined(CONFIG_OF)
@@ -448,11 +454,6 @@ static inline int dev_pm_opp_of_find_icc_paths(struct device *dev, struct opp_ta
{
return -ENOTSUPP;
}
-
-static inline int dev_pm_opp_sync_regulators(struct device *dev)
-{
- return -ENOTSUPP;
-}
#endif

#endif /* __LINUX_OPP_H__ */
--
2.29.2

2020-12-17 18:16:01

by Dmitry Osipenko

[permalink] [raw]
Subject: [PATCH v2 22/48] soc/tegra: pmc: Fix imbalanced clock disabling in error code path

The tegra_powergate_power_up() has a typo in the error code path where it
will try to disable clocks twice, fix it. In practice that error never
happens, so this is a minor correction.

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 df9a5ca8c99c..fd2ba3c59178 100644
--- a/drivers/soc/tegra/pmc.c
+++ b/drivers/soc/tegra/pmc.c
@@ -638,7 +638,7 @@ static int tegra_powergate_power_up(struct tegra_powergate *pg,

err = tegra_powergate_enable_clocks(pg);
if (err)
- goto disable_clks;
+ goto powergate_off;

usleep_range(10, 20);

--
2.29.2

2020-12-17 18:16:33

by Dmitry Osipenko

[permalink] [raw]
Subject: [PATCH v2 11/48] opp: Add dev_pm_opp_find_level_ceil()

Add a ceil version of the dev_pm_opp_find_level(). It's handy to have if
levels don't start from 0 in OPP table and zero usually means a minimal
level.

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

diff --git a/drivers/opp/core.c b/drivers/opp/core.c
index eab37b3a27bb..0783a4ac819a 100644
--- a/drivers/opp/core.c
+++ b/drivers/opp/core.c
@@ -449,6 +449,55 @@ struct dev_pm_opp *dev_pm_opp_find_level_exact(struct device *dev,
}
EXPORT_SYMBOL_GPL(dev_pm_opp_find_level_exact);

+/**
+ * dev_pm_opp_find_level_ceil() - search for an rounded up level
+ * @dev: device for which we do this operation
+ * @level: level to search for
+ *
+ * Return: Searches for rounded up match in the opp table and returns pointer
+ * to the matching opp if found, else returns ERR_PTR in case of error and
+ * should be handled using IS_ERR. Error return values can be:
+ * EINVAL: for bad pointer
+ * ERANGE: no match found for search
+ * ENODEV: if device not found in list of registered devices
+ *
+ * The callers are required to call dev_pm_opp_put() for the returned OPP after
+ * use.
+ */
+struct dev_pm_opp *dev_pm_opp_find_level_ceil(struct device *dev,
+ unsigned int *level)
+{
+ struct opp_table *opp_table;
+ struct dev_pm_opp *temp_opp, *opp = ERR_PTR(-ERANGE);
+
+ opp_table = _find_opp_table(dev);
+ if (IS_ERR(opp_table)) {
+ int r = PTR_ERR(opp_table);
+
+ dev_err(dev, "%s: OPP table not found (%d)\n", __func__, r);
+ return ERR_PTR(r);
+ }
+
+ mutex_lock(&opp_table->lock);
+
+ list_for_each_entry(temp_opp, &opp_table->opp_list, node) {
+ if (temp_opp->available && temp_opp->level >= *level) {
+ opp = temp_opp;
+ *level = opp->level;
+
+ /* Increment the reference count of OPP */
+ dev_pm_opp_get(opp);
+ break;
+ }
+ }
+
+ mutex_unlock(&opp_table->lock);
+ dev_pm_opp_put_opp_table(opp_table);
+
+ return opp;
+}
+EXPORT_SYMBOL_GPL(dev_pm_opp_find_level_ceil);
+
static noinline struct dev_pm_opp *_find_freq_ceil(struct opp_table *opp_table,
unsigned long *freq)
{
diff --git a/include/linux/pm_opp.h b/include/linux/pm_opp.h
index f311a8b2ca04..a17d92d923cc 100644
--- a/include/linux/pm_opp.h
+++ b/include/linux/pm_opp.h
@@ -111,6 +111,8 @@ struct dev_pm_opp *dev_pm_opp_find_freq_exact(struct device *dev,
bool available);
struct dev_pm_opp *dev_pm_opp_find_level_exact(struct device *dev,
unsigned int level);
+struct dev_pm_opp *dev_pm_opp_find_level_ceil(struct device *dev,
+ unsigned int *level);

struct dev_pm_opp *dev_pm_opp_find_freq_floor(struct device *dev,
unsigned long *freq);
@@ -228,6 +230,12 @@ static inline struct dev_pm_opp *dev_pm_opp_find_level_exact(struct device *dev,
return ERR_PTR(-ENOTSUPP);
}

+static inline struct dev_pm_opp *dev_pm_opp_find_level_ceil(struct device *dev,
+ unsigned int *level)
+{
+ return ERR_PTR(-ENOTSUPP);
+}
+
static inline struct dev_pm_opp *dev_pm_opp_find_freq_floor(struct device *dev,
unsigned long *freq)
{
--
2.29.2

2020-12-17 18:16:56

by Dmitry Osipenko

[permalink] [raw]
Subject: [PATCH v2 05/48] media: dt: bindings: tegra-vde: Document OPP and power domain properties

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

Signed-off-by: Dmitry Osipenko <[email protected]>
---
.../devicetree/bindings/media/nvidia,tegra-vde.txt | 12 ++++++++++++
1 file changed, 12 insertions(+)

diff --git a/Documentation/devicetree/bindings/media/nvidia,tegra-vde.txt b/Documentation/devicetree/bindings/media/nvidia,tegra-vde.txt
index 602169b8aa19..02fe2af3bd13 100644
--- a/Documentation/devicetree/bindings/media/nvidia,tegra-vde.txt
+++ b/Documentation/devicetree/bindings/media/nvidia,tegra-vde.txt
@@ -36,6 +36,16 @@ Optional properties:
- reset-names : Must include the following entries:
- mc
- iommus: Must contain phandle to the IOMMU device node.
+- operating-points-v2: See ../bindings/opp/opp.txt for details.
+- power-domains: Phandle to VDEC power domain.
+
+For each opp entry in 'operating-points-v2' table:
+- 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.

Example:

@@ -61,4 +71,6 @@ video-codec@6001a000 {
reset-names = "vde", "mc";
resets = <&tegra_car 61>, <&mc TEGRA20_MC_RESET_VDE>;
iommus = <&mc TEGRA_SWGROUP_VDE>;
+ operating-points-v2 = <&dvfs_opp_table>;
+ power-domains = <&domain>;
};
--
2.29.2

2020-12-17 18:23:16

by Mikko Perttunen

[permalink] [raw]
Subject: Re: [PATCH v2 34/48] gpu: host1x: Support power management

On 12/17/20 8:06 PM, Dmitry Osipenko wrote:
> Add suspend/resume and generic power domain support to the Host1x driver.
> This is required for enabling system-wide DVFS and supporting dynamic
> power management using a generic power domain.
>
> Tested-by: Peter Geis <[email protected]>
> Tested-by: Nicolas Chauvet <[email protected]>
> Signed-off-by: Dmitry Osipenko <[email protected]>
> ---
> drivers/gpu/host1x/dev.c | 102 ++++++++++++++++++++++++++++++++++-----
> 1 file changed, 91 insertions(+), 11 deletions(-)
>
> diff --git a/drivers/gpu/host1x/dev.c b/drivers/gpu/host1x/dev.c
> index d0ebb70e2fdd..c1525cffe7b1 100644
> --- a/drivers/gpu/host1x/dev.c
> +++ b/drivers/gpu/host1x/dev.c
> @@ -12,6 +12,7 @@
> #include <linux/module.h>
> #include <linux/of_device.h>
> #include <linux/of.h>
> +#include <linux/pm_runtime.h>
> #include <linux/slab.h>
>
> #define CREATE_TRACE_POINTS
> @@ -417,7 +418,7 @@ static int host1x_probe(struct platform_device *pdev)
> return err;
> }
>
> - host->rst = devm_reset_control_get(&pdev->dev, "host1x");
> + host->rst = devm_reset_control_get_exclusive_released(&pdev->dev, "host1x");
> if (IS_ERR(host->rst)) {
> err = PTR_ERR(host->rst);
> dev_err(&pdev->dev, "failed to get reset: %d\n", err);
> @@ -437,16 +438,15 @@ 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;
> - }
> + pm_runtime_enable(&pdev->dev);
> + err = pm_runtime_get_sync(&pdev->dev);
> + if (err < 0)
> + goto rpm_disable;
>
> err = reset_control_deassert(host->rst);
> if (err < 0) {
> dev_err(&pdev->dev, "failed to deassert reset: %d\n", err);
> - goto unprepare_disable;
> + goto rpm_disable;
> }
>
> err = host1x_syncpt_init(host);
> @@ -485,9 +485,10 @@ static int host1x_probe(struct platform_device *pdev)
> host1x_syncpt_deinit(host);
> reset_assert:
> reset_control_assert(host->rst);
> -unprepare_disable:
> - clk_disable_unprepare(host->clk);
> -free_channels:
> +rpm_disable:
> + pm_runtime_put(&pdev->dev);
> + pm_runtime_disable(&pdev->dev);
> +
> host1x_channel_list_free(&host->channel_list);
> iommu_exit:
> host1x_iommu_exit(host);
> @@ -504,16 +505,95 @@ static int host1x_remove(struct platform_device *pdev)
> host1x_intr_deinit(host);
> host1x_syncpt_deinit(host);
> reset_control_assert(host->rst);
> - clk_disable_unprepare(host->clk);
> + pm_runtime_put(&pdev->dev);
> + pm_runtime_disable(&pdev->dev);
> host1x_iommu_exit(host);
>
> return 0;
> }
>
> +static int __maybe_unused host1x_runtime_suspend(struct device *dev)
> +{
> + struct host1x *host = dev_get_drvdata(dev);
> +
> + clk_disable_unprepare(host->clk);
> + reset_control_release(host->rst);
> +
> + return 0;
> +}
> +
> +static int __maybe_unused host1x_runtime_resume(struct device *dev)
> +{
> + struct host1x *host = dev_get_drvdata(dev);
> + int err;
> +
> + err = reset_control_acquire(host->rst);
> + 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;
> + }
> +
> + return 0;
> +
> +release_reset:
> + reset_control_release(host->rst);
> +
> + return err;
> +}
> +
> +static __maybe_unused int host1x_suspend(struct device *dev)
> +{
> + struct host1x *host = dev_get_drvdata(dev);
> + int err;
> +
> + host1x_syncpt_save(host);
> +
> + err = pm_runtime_force_suspend(dev);
> + if (err < 0)
> + return err;
> +
> + return 0;
> +}
> +
> +static __maybe_unused int host1x_resume(struct device *dev)
> +{
> + struct host1x *host = dev_get_drvdata(dev);
> + struct host1x_channel *channel;
> + unsigned int index;
> + int err;
> +
> + err = pm_runtime_force_resume(dev);
> + if (err < 0)
> + return err;
> +
> + host1x_syncpt_restore(host);

We also need to execute 'host1x_setup_sid_table' upon resume.

cheers,
Mikko

> +
> + for_each_set_bit(index, host->channel_list.allocated_channels,
> + host->info->nb_channels) {
> + channel = &host->channel_list.channels[index];
> + host1x_hw_channel_init(host, channel, channel->id);
> + }
> +
> + return 0;
> +}
> +
> +static const struct dev_pm_ops host1x_pm = {
> + SET_RUNTIME_PM_OPS(host1x_runtime_suspend, host1x_runtime_resume,
> + NULL)
> + SET_SYSTEM_SLEEP_PM_OPS(host1x_suspend, host1x_resume)
> +};
> +
> 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,
>

2020-12-17 18:30:28

by Daniel Lezcano

[permalink] [raw]
Subject: Re: [PATCH v2 47/48] ARM: tegra: ventana: Support CPU voltage scaling and thermal throttling

On 17/12/2020 19:06, Dmitry Osipenko wrote:
> Enable CPU voltage scaling and thermal throttling on Tegra20 Ventana board.
>
> Signed-off-by: Dmitry Osipenko <[email protected]>
> ---
> arch/arm/boot/dts/tegra20-ventana.dts | 40 ++++++++++++++++++++++++++-
> 1 file changed, 39 insertions(+), 1 deletion(-)
>
> diff --git a/arch/arm/boot/dts/tegra20-ventana.dts b/arch/arm/boot/dts/tegra20-ventana.dts
> index 14ace2ef749c..c2d9f38960bc 100644
> --- a/arch/arm/boot/dts/tegra20-ventana.dts
> +++ b/arch/arm/boot/dts/tegra20-ventana.dts
> @@ -2,8 +2,10 @@
> /dts-v1/;
>
> #include <dt-bindings/input/input.h>
> +#include <dt-bindings/thermal/thermal.h>
> #include "tegra20.dtsi"
> #include "tegra20-cpu-opp.dtsi"
> +#include "tegra20-cpu-opp-microvolt.dtsi"
>
> / {
> model = "NVIDIA Tegra20 Ventana evaluation board";
> @@ -527,9 +529,10 @@ ldo_rtc {
> };
> };
>
> - temperature-sensor@4c {
> + nct1008: temperature-sensor@4c {
> compatible = "onnn,nct1008";
> reg = <0x4c>;
> + #thermal-sensor-cells = <1>;
> };
> };
>
> @@ -615,10 +618,13 @@ clk32k_in: clock@0 {
>
> cpus {
> cpu0: cpu@0 {
> + cpu-supply = <&vdd_cpu>;
> operating-points-v2 = <&cpu0_opp_table>;
> + #cooling-cells = <2>;
> };
>
> cpu@1 {
> + cpu-supply = <&vdd_cpu>;
> operating-points-v2 = <&cpu0_opp_table>;
> };
> };
> @@ -717,4 +723,36 @@ sound {
> <&tegra_car TEGRA20_CLK_CDEV1>;
> clock-names = "pll_a", "pll_a_out0", "mclk";
> };
> +
> + thermal-zones {
> + cpu-thermal {
> + polling-delay-passive = <1000>; /* milliseconds */
> + polling-delay = <5000>; /* milliseconds */
> +
> + thermal-sensors = <&nct1008 1>;
> +
> + trips {
> + trip0: cpu-alert0 {
> + /* start throttling at 50C */
> + temperature = <50000>;
> + hysteresis = <200>;

Did you mean <2000> ?

> + type = "passive";
> + };
> +
> + trip1: cpu-crit {
> + /* shut down at 60C */
> + temperature = <60000>;
> + hysteresis = <2000>;

I think you can drop the hysteresis here, when the critical temperature
is reached, there is an emergency shutdown.

50°C and 60°C sound very low values, no ?

> + type = "critical";
> + };
> + };
> +
> + cooling-maps {
> + map0 {
> + trip = <&trip0>;
> + cooling-device = <&cpu0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>;

You should add all CPUs here.

> + };
> + };
> + };
> + };
> };
>


--
<http://www.linaro.org/> Linaro.org │ Open source software for ARM SoCs

Follow Linaro: <http://www.facebook.com/pages/Linaro> Facebook |
<http://twitter.com/#!/linaroorg> Twitter |
<http://www.linaro.org/linaro-blog/> Blog

2020-12-17 18:30:44

by Daniel Lezcano

[permalink] [raw]
Subject: Re: [PATCH v2 48/48] ARM: tegra: cardhu: Support CPU voltage scaling and thermal throttling

On 17/12/2020 19:06, Dmitry Osipenko wrote:
> Enable CPU voltage scaling and thermal throttling on Tegra30 Cardhu board.
>
> Signed-off-by: Dmitry Osipenko <[email protected]>
> ---

Same comments as 47/48



> arch/arm/boot/dts/tegra30-cardhu.dtsi | 61 ++++++++++++++++++++++++++-
> 1 file changed, 60 insertions(+), 1 deletion(-)
>
> diff --git a/arch/arm/boot/dts/tegra30-cardhu.dtsi b/arch/arm/boot/dts/tegra30-cardhu.dtsi
> index d74c9ca78a7f..08c0ea4e6228 100644
> --- a/arch/arm/boot/dts/tegra30-cardhu.dtsi
> +++ b/arch/arm/boot/dts/tegra30-cardhu.dtsi
> @@ -1,6 +1,9 @@
> // SPDX-License-Identifier: GPL-2.0
> #include <dt-bindings/input/input.h>
> +#include <dt-bindings/thermal/thermal.h>
> #include "tegra30.dtsi"
> +#include "tegra30-cpu-opp.dtsi"
> +#include "tegra30-cpu-opp-microvolt.dtsi"
>
> /**
> * This file contains common DT entry for all fab version of Cardhu.
> @@ -339,12 +342,13 @@ ldo8_reg: ldo8 {
> };
> };
>
> - temperature-sensor@4c {
> + nct1008: temperature-sensor@4c {
> compatible = "onnn,nct1008";
> reg = <0x4c>;
> vcc-supply = <&sys_3v3_reg>;
> interrupt-parent = <&gpio>;
> interrupts = <TEGRA_GPIO(CC, 2) IRQ_TYPE_LEVEL_LOW>;
> + #thermal-sensor-cells = <1>;
> };
>
> vdd_core: tps62361@60 {
> @@ -438,6 +442,29 @@ clk32k_in: clock@0 {
> #clock-cells = <0>;
> };
>
> + cpus {
> + cpu0: cpu@0 {
> + cpu-supply = <&vddctrl_reg>;
> + operating-points-v2 = <&cpu0_opp_table>;
> + #cooling-cells = <2>;
> + };
> +
> + cpu@1 {
> + cpu-supply = <&vddctrl_reg>;
> + operating-points-v2 = <&cpu0_opp_table>;
> + };
> +
> + cpu@2 {
> + cpu-supply = <&vddctrl_reg>;
> + operating-points-v2 = <&cpu0_opp_table>;
> + };
> +
> + cpu@3 {
> + cpu-supply = <&vddctrl_reg>;
> + operating-points-v2 = <&cpu0_opp_table>;
> + };
> + };
> +
> panel: panel {
> compatible = "chunghwa,claa101wb01";
> ddc-i2c-bus = <&panelddc>;
> @@ -617,6 +644,38 @@ sound {
> <&tegra_car TEGRA30_CLK_EXTERN1>;
> };
>
> + thermal-zones {
> + cpu-thermal {
> + polling-delay-passive = <1000>; /* milliseconds */
> + polling-delay = <5000>; /* milliseconds */
> +
> + thermal-sensors = <&nct1008 1>;
> +
> + trips {
> + trip0: cpu-alert0 {
> + /* throttle at 57C until temperature drops to 56.8C */
> + temperature = <57000>;
> + hysteresis = <200>;
> + type = "passive";
> + };
> +
> + trip1: cpu-crit {
> + /* shut down at 60C */
> + temperature = <60000>;
> + hysteresis = <2000>;
> + type = "critical";
> + };
> + };
> +
> + cooling-maps {
> + map0 {
> + trip = <&trip0>;
> + cooling-device = <&cpu0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>;
> + };
> + };
> + };
> + };
> +
> gpio-keys {
> compatible = "gpio-keys";
>
>


--
<http://www.linaro.org/> Linaro.org │ Open source software for ARM SoCs

Follow Linaro: <http://www.facebook.com/pages/Linaro> Facebook |
<http://twitter.com/#!/linaroorg> Twitter |
<http://www.linaro.org/linaro-blog/> Blog

2020-12-17 18:48:27

by Dmitry Osipenko

[permalink] [raw]
Subject: Re: [PATCH v2 34/48] gpu: host1x: Support power management

17.12.2020 21:21, Mikko Perttunen пишет:
> On 12/17/20 8:06 PM, Dmitry Osipenko wrote:
>> Add suspend/resume and generic power domain support to the Host1x driver.
>> This is required for enabling system-wide DVFS and supporting dynamic
>> power management using a generic power domain.
>>
>> Tested-by: Peter Geis <[email protected]>
>> Tested-by: Nicolas Chauvet <[email protected]>
>> Signed-off-by: Dmitry Osipenko <[email protected]>
>> ---
>>   drivers/gpu/host1x/dev.c | 102 ++++++++++++++++++++++++++++++++++-----
>>   1 file changed, 91 insertions(+), 11 deletions(-)
>>
>> diff --git a/drivers/gpu/host1x/dev.c b/drivers/gpu/host1x/dev.c
>> index d0ebb70e2fdd..c1525cffe7b1 100644
>> --- a/drivers/gpu/host1x/dev.c
>> +++ b/drivers/gpu/host1x/dev.c
>> @@ -12,6 +12,7 @@
>>   #include <linux/module.h>
>>   #include <linux/of_device.h>
>>   #include <linux/of.h>
>> +#include <linux/pm_runtime.h>
>>   #include <linux/slab.h>
>>     #define CREATE_TRACE_POINTS
>> @@ -417,7 +418,7 @@ static int host1x_probe(struct platform_device *pdev)
>>           return err;
>>       }
>>   -    host->rst = devm_reset_control_get(&pdev->dev, "host1x");
>> +    host->rst = devm_reset_control_get_exclusive_released(&pdev->dev,
>> "host1x");
>>       if (IS_ERR(host->rst)) {
>>           err = PTR_ERR(host->rst);
>>           dev_err(&pdev->dev, "failed to get reset: %d\n", err);
>> @@ -437,16 +438,15 @@ 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;
>> -    }
>> +    pm_runtime_enable(&pdev->dev);
>> +    err = pm_runtime_get_sync(&pdev->dev);
>> +    if (err < 0)
>> +        goto rpm_disable;
>>         err = reset_control_deassert(host->rst);
>>       if (err < 0) {
>>           dev_err(&pdev->dev, "failed to deassert reset: %d\n", err);
>> -        goto unprepare_disable;
>> +        goto rpm_disable;
>>       }
>>         err = host1x_syncpt_init(host);
>> @@ -485,9 +485,10 @@ static int host1x_probe(struct platform_device
>> *pdev)
>>       host1x_syncpt_deinit(host);
>>   reset_assert:
>>       reset_control_assert(host->rst);
>> -unprepare_disable:
>> -    clk_disable_unprepare(host->clk);
>> -free_channels:
>> +rpm_disable:
>> +    pm_runtime_put(&pdev->dev);
>> +    pm_runtime_disable(&pdev->dev);
>> +
>>       host1x_channel_list_free(&host->channel_list);
>>   iommu_exit:
>>       host1x_iommu_exit(host);
>> @@ -504,16 +505,95 @@ static int host1x_remove(struct platform_device
>> *pdev)
>>       host1x_intr_deinit(host);
>>       host1x_syncpt_deinit(host);
>>       reset_control_assert(host->rst);
>> -    clk_disable_unprepare(host->clk);
>> +    pm_runtime_put(&pdev->dev);
>> +    pm_runtime_disable(&pdev->dev);
>>       host1x_iommu_exit(host);
>>         return 0;
>>   }
>>   +static int __maybe_unused host1x_runtime_suspend(struct device *dev)
>> +{
>> +    struct host1x *host = dev_get_drvdata(dev);
>> +
>> +    clk_disable_unprepare(host->clk);
>> +    reset_control_release(host->rst);
>> +
>> +    return 0;
>> +}
>> +
>> +static int __maybe_unused host1x_runtime_resume(struct device *dev)
>> +{
>> +    struct host1x *host = dev_get_drvdata(dev);
>> +    int err;
>> +
>> +    err = reset_control_acquire(host->rst);
>> +    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;
>> +    }
>> +
>> +    return 0;
>> +
>> +release_reset:
>> +    reset_control_release(host->rst);
>> +
>> +    return err;
>> +}
>> +
>> +static __maybe_unused int host1x_suspend(struct device *dev)
>> +{
>> +    struct host1x *host = dev_get_drvdata(dev);
>> +    int err;
>> +
>> +    host1x_syncpt_save(host);
>> +
>> +    err = pm_runtime_force_suspend(dev);
>> +    if (err < 0)
>> +        return err;
>> +
>> +    return 0;
>> +}
>> +
>> +static __maybe_unused int host1x_resume(struct device *dev)
>> +{
>> +    struct host1x *host = dev_get_drvdata(dev);
>> +    struct host1x_channel *channel;
>> +    unsigned int index;
>> +    int err;
>> +
>> +    err = pm_runtime_force_resume(dev);
>> +    if (err < 0)
>> +        return err;
>> +
>> +    host1x_syncpt_restore(host);
>
> We also need to execute 'host1x_setup_sid_table' upon resume.

Indeed, thanks. I'll correct it in the next revision.

Perhaps the actual save/restore needs to be moved to the runtime
callbacks. At least I can't remember right now why this wasn't done in
the first place.

2020-12-17 19:02:36

by Dmitry Osipenko

[permalink] [raw]
Subject: Re: [PATCH v2 47/48] ARM: tegra: ventana: Support CPU voltage scaling and thermal throttling

17.12.2020 21:28, Daniel Lezcano пишет:
> On 17/12/2020 19:06, Dmitry Osipenko wrote:
>> Enable CPU voltage scaling and thermal throttling on Tegra20 Ventana board.
>>
>> Signed-off-by: Dmitry Osipenko <[email protected]>
>> ---
>> arch/arm/boot/dts/tegra20-ventana.dts | 40 ++++++++++++++++++++++++++-
>> 1 file changed, 39 insertions(+), 1 deletion(-)
>>
>> diff --git a/arch/arm/boot/dts/tegra20-ventana.dts b/arch/arm/boot/dts/tegra20-ventana.dts
>> index 14ace2ef749c..c2d9f38960bc 100644
>> --- a/arch/arm/boot/dts/tegra20-ventana.dts
>> +++ b/arch/arm/boot/dts/tegra20-ventana.dts
>> @@ -2,8 +2,10 @@
>> /dts-v1/;
>>
>> #include <dt-bindings/input/input.h>
>> +#include <dt-bindings/thermal/thermal.h>
>> #include "tegra20.dtsi"
>> #include "tegra20-cpu-opp.dtsi"
>> +#include "tegra20-cpu-opp-microvolt.dtsi"
>>
>> / {
>> model = "NVIDIA Tegra20 Ventana evaluation board";
>> @@ -527,9 +529,10 @@ ldo_rtc {
>> };
>> };
>>
>> - temperature-sensor@4c {
>> + nct1008: temperature-sensor@4c {
>> compatible = "onnn,nct1008";
>> reg = <0x4c>;
>> + #thermal-sensor-cells = <1>;
>> };
>> };
>>
>> @@ -615,10 +618,13 @@ clk32k_in: clock@0 {
>>
>> cpus {
>> cpu0: cpu@0 {
>> + cpu-supply = <&vdd_cpu>;
>> operating-points-v2 = <&cpu0_opp_table>;
>> + #cooling-cells = <2>;
>> };
>>
>> cpu@1 {
>> + cpu-supply = <&vdd_cpu>;
>> operating-points-v2 = <&cpu0_opp_table>;
>> };
>> };
>> @@ -717,4 +723,36 @@ sound {
>> <&tegra_car TEGRA20_CLK_CDEV1>;
>> clock-names = "pll_a", "pll_a_out0", "mclk";
>> };
>> +
>> + thermal-zones {
>> + cpu-thermal {
>> + polling-delay-passive = <1000>; /* milliseconds */
>> + polling-delay = <5000>; /* milliseconds */
>> +
>> + thermal-sensors = <&nct1008 1>;
>> +
>> + trips {
>> + trip0: cpu-alert0 {
>> + /* start throttling at 50C */
>> + temperature = <50000>;
>> + hysteresis = <200>;
>
> Did you mean <2000> ?

The <200> is correct.

Please see this commit for example:

https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=2b22393b27670b45a2c870bce3df6579efd9a86a

>> + type = "passive";
>> + };
>> +
>> + trip1: cpu-crit {
>> + /* shut down at 60C */
>> + temperature = <60000>;
>> + hysteresis = <2000>;
>
> I think you can drop the hysteresis here, when the critical temperature
> is reached, there is an emergency shutdown.

Yes, perhaps you're right. Hysteresis doesn't make sense for a critical
trip. I'll improve it in the next revision, thanks.

> 50°C and 60°C sound very low values, no ?

Tegra20 CPU is very cold in comparison to later generations, it should
stay under 50°C even at a full load.

>> + type = "critical";
>> + };
>> + };
>> +
>> + cooling-maps {
>> + map0 {
>> + trip = <&trip0>;
>> + cooling-device = <&cpu0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>;
>
> You should add all CPUs here.


All CPU cores are coupled on Tegra in regards to CPUFreq, hence I think
it won't make any difference if secondary CPU cores will be added here,
isn't it?

2020-12-17 19:38:41

by Daniel Lezcano

[permalink] [raw]
Subject: Re: [PATCH v2 47/48] ARM: tegra: ventana: Support CPU voltage scaling and thermal throttling

On 17/12/2020 20:01, Dmitry Osipenko wrote:
> 17.12.2020 21:28, Daniel Lezcano пишет:
>> On 17/12/2020 19:06, Dmitry Osipenko wrote:
>>> Enable CPU voltage scaling and thermal throttling on Tegra20 Ventana board.
>>>
>>> Signed-off-by: Dmitry Osipenko <[email protected]>
>>> ---
>>> arch/arm/boot/dts/tegra20-ventana.dts | 40 ++++++++++++++++++++++++++-
>>> 1 file changed, 39 insertions(+), 1 deletion(-)
>>>
>>> diff --git a/arch/arm/boot/dts/tegra20-ventana.dts b/arch/arm/boot/dts/tegra20-ventana.dts
>>> index 14ace2ef749c..c2d9f38960bc 100644
>>> --- a/arch/arm/boot/dts/tegra20-ventana.dts
>>> +++ b/arch/arm/boot/dts/tegra20-ventana.dts
>>> @@ -2,8 +2,10 @@
>>> /dts-v1/;
>>>
>>> #include <dt-bindings/input/input.h>
>>> +#include <dt-bindings/thermal/thermal.h>
>>> #include "tegra20.dtsi"
>>> #include "tegra20-cpu-opp.dtsi"
>>> +#include "tegra20-cpu-opp-microvolt.dtsi"
>>>
>>> / {
>>> model = "NVIDIA Tegra20 Ventana evaluation board";
>>> @@ -527,9 +529,10 @@ ldo_rtc {
>>> };
>>> };
>>>
>>> - temperature-sensor@4c {
>>> + nct1008: temperature-sensor@4c {
>>> compatible = "onnn,nct1008";
>>> reg = <0x4c>;
>>> + #thermal-sensor-cells = <1>;
>>> };
>>> };
>>>
>>> @@ -615,10 +618,13 @@ clk32k_in: clock@0 {
>>>
>>> cpus {
>>> cpu0: cpu@0 {
>>> + cpu-supply = <&vdd_cpu>;
>>> operating-points-v2 = <&cpu0_opp_table>;
>>> + #cooling-cells = <2>;
>>> };
>>>
>>> cpu@1 {
>>> + cpu-supply = <&vdd_cpu>;
>>> operating-points-v2 = <&cpu0_opp_table>;
>>> };
>>> };
>>> @@ -717,4 +723,36 @@ sound {
>>> <&tegra_car TEGRA20_CLK_CDEV1>;
>>> clock-names = "pll_a", "pll_a_out0", "mclk";
>>> };
>>> +
>>> + thermal-zones {
>>> + cpu-thermal {
>>> + polling-delay-passive = <1000>; /* milliseconds */
>>> + polling-delay = <5000>; /* milliseconds */
>>> +
>>> + thermal-sensors = <&nct1008 1>;
>>> +
>>> + trips {
>>> + trip0: cpu-alert0 {
>>> + /* start throttling at 50C */
>>> + temperature = <50000>;
>>> + hysteresis = <200>;
>>
>> Did you mean <2000> ?
>
> The <200> is correct.
>
> Please see this commit for example:
>
> https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=2b22393b27670b45a2c870bce3df6579efd9a86a

Ok, thanks for the pointer.

>>> + type = "passive";
>>> + };
>>> +
>>> + trip1: cpu-crit {
>>> + /* shut down at 60C */
>>> + temperature = <60000>;
>>> + hysteresis = <2000>;
>>
>> I think you can drop the hysteresis here, when the critical temperature
>> is reached, there is an emergency shutdown.
>
> Yes, perhaps you're right. Hysteresis doesn't make sense for a critical
> trip. I'll improve it in the next revision, thanks.
>
>> 50°C and 60°C sound very low values, no ?
>
> Tegra20 CPU is very cold in comparison to later generations, it should
> stay under 50°C even at a full load.

Interesting, thanks.

>>> + type = "critical";
>>> + };
>>> + };
>>> +
>>> + cooling-maps {
>>> + map0 {
>>> + trip = <&trip0>;
>>> + cooling-device = <&cpu0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>;
>>
>> You should add all CPUs here.
>
>
> All CPU cores are coupled on Tegra in regards to CPUFreq, hence I think
> it won't make any difference if secondary CPU cores will be added here,
> isn't it?

The explanation is in the description of commit ef4734500407ce4d


--
<http://www.linaro.org/> Linaro.org │ Open source software for ARM SoCs

Follow Linaro: <http://www.facebook.com/pages/Linaro> Facebook |
<http://twitter.com/#!/linaroorg> Twitter |
<http://www.linaro.org/linaro-blog/> Blog

2020-12-17 20:31:54

by Dmitry Osipenko

[permalink] [raw]
Subject: Re: [PATCH v2 47/48] ARM: tegra: ventana: Support CPU voltage scaling and thermal throttling

17.12.2020 22:36, Daniel Lezcano пишет:
>>>> + type = "critical";
>>>> + };
>>>> + };
>>>> +
>>>> + cooling-maps {
>>>> + map0 {
>>>> + trip = <&trip0>;
>>>> + cooling-device = <&cpu0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>;
>>> You should add all CPUs here.
>>
>> All CPU cores are coupled on Tegra in regards to CPUFreq, hence I think
>> it won't make any difference if secondary CPU cores will be added here,
>> isn't it?
> The explanation is in the description of commit ef4734500407ce4d

I think that really only makes sense if CPU cores have independent clock
rate management. IIRC, I actually made some research about this in the
past and intentionally removed the secondary cores from the
cooling-device since they didn't make any difference for a coupled CPU
cores.

That commit also says:

"But as soon as this CPU ordering changes and any other CPU is used to
bring up the cooling device, we will start seeing failures."

I don't quite understand to what "failures" that commit referrers. I
tried to change the cpu0 to cpu1 in the cooling-device and don't see any
failures. Could you please clarify this?

In general it should be fine to add all the cores to the cooling-device
and I'll do it in v3, but I want to make it clear why this is needed.

2020-12-17 21:00:45

by Dmitry Osipenko

[permalink] [raw]
Subject: Re: [PATCH v2 34/48] gpu: host1x: Support power management

17.12.2020 21:45, Dmitry Osipenko пишет:
> 17.12.2020 21:21, Mikko Perttunen пишет:
>> On 12/17/20 8:06 PM, Dmitry Osipenko wrote:
>>> Add suspend/resume and generic power domain support to the Host1x driver.
>>> This is required for enabling system-wide DVFS and supporting dynamic
>>> power management using a generic power domain.
>>>
>>> Tested-by: Peter Geis <[email protected]>
>>> Tested-by: Nicolas Chauvet <[email protected]>
>>> Signed-off-by: Dmitry Osipenko <[email protected]>
>>> ---
>>>   drivers/gpu/host1x/dev.c | 102 ++++++++++++++++++++++++++++++++++-----
>>>   1 file changed, 91 insertions(+), 11 deletions(-)
>>>
>>> diff --git a/drivers/gpu/host1x/dev.c b/drivers/gpu/host1x/dev.c
>>> index d0ebb70e2fdd..c1525cffe7b1 100644
>>> --- a/drivers/gpu/host1x/dev.c
>>> +++ b/drivers/gpu/host1x/dev.c
>>> @@ -12,6 +12,7 @@
>>>   #include <linux/module.h>
>>>   #include <linux/of_device.h>
>>>   #include <linux/of.h>
>>> +#include <linux/pm_runtime.h>
>>>   #include <linux/slab.h>
>>>     #define CREATE_TRACE_POINTS
>>> @@ -417,7 +418,7 @@ static int host1x_probe(struct platform_device *pdev)
>>>           return err;
>>>       }
>>>   -    host->rst = devm_reset_control_get(&pdev->dev, "host1x");
>>> +    host->rst = devm_reset_control_get_exclusive_released(&pdev->dev,
>>> "host1x");
>>>       if (IS_ERR(host->rst)) {
>>>           err = PTR_ERR(host->rst);
>>>           dev_err(&pdev->dev, "failed to get reset: %d\n", err);
>>> @@ -437,16 +438,15 @@ 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;
>>> -    }
>>> +    pm_runtime_enable(&pdev->dev);
>>> +    err = pm_runtime_get_sync(&pdev->dev);
>>> +    if (err < 0)
>>> +        goto rpm_disable;
>>>         err = reset_control_deassert(host->rst);
>>>       if (err < 0) {
>>>           dev_err(&pdev->dev, "failed to deassert reset: %d\n", err);
>>> -        goto unprepare_disable;
>>> +        goto rpm_disable;
>>>       }
>>>         err = host1x_syncpt_init(host);
>>> @@ -485,9 +485,10 @@ static int host1x_probe(struct platform_device
>>> *pdev)
>>>       host1x_syncpt_deinit(host);
>>>   reset_assert:
>>>       reset_control_assert(host->rst);
>>> -unprepare_disable:
>>> -    clk_disable_unprepare(host->clk);
>>> -free_channels:
>>> +rpm_disable:
>>> +    pm_runtime_put(&pdev->dev);
>>> +    pm_runtime_disable(&pdev->dev);
>>> +
>>>       host1x_channel_list_free(&host->channel_list);
>>>   iommu_exit:
>>>       host1x_iommu_exit(host);
>>> @@ -504,16 +505,95 @@ static int host1x_remove(struct platform_device
>>> *pdev)
>>>       host1x_intr_deinit(host);
>>>       host1x_syncpt_deinit(host);
>>>       reset_control_assert(host->rst);
>>> -    clk_disable_unprepare(host->clk);
>>> +    pm_runtime_put(&pdev->dev);
>>> +    pm_runtime_disable(&pdev->dev);
>>>       host1x_iommu_exit(host);
>>>         return 0;
>>>   }
>>>   +static int __maybe_unused host1x_runtime_suspend(struct device *dev)
>>> +{
>>> +    struct host1x *host = dev_get_drvdata(dev);
>>> +
>>> +    clk_disable_unprepare(host->clk);
>>> +    reset_control_release(host->rst);
>>> +
>>> +    return 0;
>>> +}
>>> +
>>> +static int __maybe_unused host1x_runtime_resume(struct device *dev)
>>> +{
>>> +    struct host1x *host = dev_get_drvdata(dev);
>>> +    int err;
>>> +
>>> +    err = reset_control_acquire(host->rst);
>>> +    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;
>>> +    }
>>> +
>>> +    return 0;
>>> +
>>> +release_reset:
>>> +    reset_control_release(host->rst);
>>> +
>>> +    return err;
>>> +}
>>> +
>>> +static __maybe_unused int host1x_suspend(struct device *dev)
>>> +{
>>> +    struct host1x *host = dev_get_drvdata(dev);
>>> +    int err;
>>> +
>>> +    host1x_syncpt_save(host);
>>> +
>>> +    err = pm_runtime_force_suspend(dev);
>>> +    if (err < 0)
>>> +        return err;
>>> +
>>> +    return 0;
>>> +}
>>> +
>>> +static __maybe_unused int host1x_resume(struct device *dev)
>>> +{
>>> +    struct host1x *host = dev_get_drvdata(dev);
>>> +    struct host1x_channel *channel;
>>> +    unsigned int index;
>>> +    int err;
>>> +
>>> +    err = pm_runtime_force_resume(dev);
>>> +    if (err < 0)
>>> +        return err;
>>> +
>>> +    host1x_syncpt_restore(host);
>>
>> We also need to execute 'host1x_setup_sid_table' upon resume.
>
> Indeed, thanks. I'll correct it in the next revision.
>
> Perhaps the actual save/restore needs to be moved to the runtime
> callbacks. At least I can't remember right now why this wasn't done in
> the first place.
>

I looked at the save/restore once again and recalled why it's done so.
The reason is that the host1x touches hardware during the driver probe,
and thus, RPM needs to be resumed first. It will be a bigger change to
properly decouple the hardware initialization so that it all could be
put it into the RPM callback. I'll try to do it in v3.

2020-12-17 21:23:22

by Daniel Lezcano

[permalink] [raw]
Subject: Re: [PATCH v2 47/48] ARM: tegra: ventana: Support CPU voltage scaling and thermal throttling

On 17/12/2020 21:28, Dmitry Osipenko wrote:
> 17.12.2020 22:36, Daniel Lezcano пишет:
>>>>> + type = "critical";
>>>>> + };
>>>>> + };
>>>>> +
>>>>> + cooling-maps {
>>>>> + map0 {
>>>>> + trip = <&trip0>;
>>>>> + cooling-device = <&cpu0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>;
>>>> You should add all CPUs here.
>>>
>>> All CPU cores are coupled on Tegra in regards to CPUFreq, hence I think
>>> it won't make any difference if secondary CPU cores will be added here,
>>> isn't it?
>> The explanation is in the description of commit ef4734500407ce4d
>
> I think that really only makes sense if CPU cores have independent clock
> rate management.

ATM I did not see any ARM platform having a clock line per CPU but I may
be wrong.

> IIRC, I actually made some research about this in the
> past and intentionally removed the secondary cores from the
> cooling-device since they didn't make any difference for a coupled CPU
> cores.
>
> That commit also says:
>
> "But as soon as this CPU ordering changes and any other CPU is used to
> bring up the cooling device, we will start seeing failures."
>
> I don't quite understand to what "failures" that commit referrers. I
> tried to change the cpu0 to cpu1 in the cooling-device and don't see any
> failures. Could you please clarify this?
>
> In general it should be fine to add all the cores to the cooling-device
> and I'll do it in v3, but I want to make it clear why this is needed.

AFAIR, if CPU0 is unplugged the cooling device can not rebind to CPU1.
And if CPU0 is plugged in again, the cooling device fails to initialize.

And, if the CPUs are mapped with the physical CPU0 to Linux numbering
CPU1, the cooling device mapping will fail.



--
<http://www.linaro.org/> Linaro.org │ Open source software for ARM SoCs

Follow Linaro: <http://www.facebook.com/pages/Linaro> Facebook |
<http://twitter.com/#!/linaroorg> Twitter |
<http://www.linaro.org/linaro-blog/> Blog

2020-12-17 21:58:35

by Dmitry Osipenko

[permalink] [raw]
Subject: Re: [PATCH v2 47/48] ARM: tegra: ventana: Support CPU voltage scaling and thermal throttling

18.12.2020 00:19, Daniel Lezcano пишет:
> On 17/12/2020 21:28, Dmitry Osipenko wrote:
>> 17.12.2020 22:36, Daniel Lezcano пишет:
>>>>>> + type = "critical";
>>>>>> + };
>>>>>> + };
>>>>>> +
>>>>>> + cooling-maps {
>>>>>> + map0 {
>>>>>> + trip = <&trip0>;
>>>>>> + cooling-device = <&cpu0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>;
>>>>> You should add all CPUs here.
>>>>
>>>> All CPU cores are coupled on Tegra in regards to CPUFreq, hence I think
>>>> it won't make any difference if secondary CPU cores will be added here,
>>>> isn't it?
>>> The explanation is in the description of commit ef4734500407ce4d
>>
>> I think that really only makes sense if CPU cores have independent clock
>> rate management.
>
> ATM I did not see any ARM platform having a clock line per CPU but I may
> be wrong.
>
>> IIRC, I actually made some research about this in the
>> past and intentionally removed the secondary cores from the
>> cooling-device since they didn't make any difference for a coupled CPU
>> cores.
>>
>> That commit also says:
>>
>> "But as soon as this CPU ordering changes and any other CPU is used to
>> bring up the cooling device, we will start seeing failures."
>>
>> I don't quite understand to what "failures" that commit referrers. I
>> tried to change the cpu0 to cpu1 in the cooling-device and don't see any
>> failures. Could you please clarify this?
>>
>> In general it should be fine to add all the cores to the cooling-device
>> and I'll do it in v3, but I want to make it clear why this is needed.
>
> AFAIR, if CPU0 is unplugged the cooling device can not rebind to CPU1.
> And if CPU0 is plugged in again, the cooling device fails to initialize.
>
> And, if the CPUs are mapped with the physical CPU0 to Linux numbering
> CPU1, the cooling device mapping will fail.

Alright, thank you.

2020-12-18 07:32:28

by Viresh Kumar

[permalink] [raw]
Subject: Re: [PATCH v2 00/48] Introduce core voltage scaling for NVIDIA Tegra20/30 SoCs

On 17-12-20, 21:05, Dmitry Osipenko wrote:
> Introduce core voltage scaling for NVIDIA Tegra20/30 SoCs, which reduces
> power consumption and heating of the Tegra chips. Tegra SoC has multiple
> hardware units which belong to a core power domain of the SoC and share
> the core voltage. The voltage must be selected in accordance to a minimum
> requirement of every core hardware unit.

Please submit the OPP changes separately (alone), it will never get
merged this way. Send fixes at the top, any features you want later in
the series. It is fine for you to base your series of patches which
are under review, you just need to mention that in your cover letter
for your platform's patchset.

--
viresh

2020-12-18 13:59:01

by Dmitry Osipenko

[permalink] [raw]
Subject: Re: [PATCH v2 00/48] Introduce core voltage scaling for NVIDIA Tegra20/30 SoCs

18.12.2020 10:14, Viresh Kumar пишет:
> On 17-12-20, 21:05, Dmitry Osipenko wrote:
>> Introduce core voltage scaling for NVIDIA Tegra20/30 SoCs, which reduces
>> power consumption and heating of the Tegra chips. Tegra SoC has multiple
>> hardware units which belong to a core power domain of the SoC and share
>> the core voltage. The voltage must be selected in accordance to a minimum
>> requirement of every core hardware unit.
>
> Please submit the OPP changes separately (alone), it will never get
> merged this way. Send fixes at the top, any features you want later in
> the series. It is fine for you to base your series of patches which
> are under review, you just need to mention that in your cover letter
> for your platform's patchset.
>

Alright, although I haven't pretended that v2 patches should be merged
right away since they are fundamentally different from v1, and thus, all
patches need to be reviewed first.

If the current OPP changes look good to you, then please give yours r-b
to the patches. Thanks in advance!

2020-12-19 10:59:55

by Krzysztof Kozlowski

[permalink] [raw]
Subject: Re: [PATCH v2 07/48] dt-bindings: arm: tegra: Add binding for core power domain

On Thu, Dec 17, 2020 at 09:05:57PM +0300, Dmitry Osipenko wrote:
> All NVIDIA Tegra SoCs have a core power domain where majority of hardware
> blocks reside. Add binding for the core power domain.
>
> Signed-off-by: Dmitry Osipenko <[email protected]>
> ---
> .../arm/tegra/nvidia,tegra20-core-domain.yaml | 48 +++++++++++++++++++
> 1 file changed, 48 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/arm/tegra/nvidia,tegra20-core-domain.yaml
>
> diff --git a/Documentation/devicetree/bindings/arm/tegra/nvidia,tegra20-core-domain.yaml b/Documentation/devicetree/bindings/arm/tegra/nvidia,tegra20-core-domain.yaml
> new file mode 100644
> index 000000000000..f3d8fd2d8371
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/arm/tegra/nvidia,tegra20-core-domain.yaml
> @@ -0,0 +1,48 @@
> +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/arm/tegra/nvidia,tegra20-core-domain.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: NVIDIA Tegra Core Power Domain
> +
> +maintainers:
> + - Dmitry Osipenko <[email protected]>
> + - Jon Hunter <[email protected]>
> + - Thierry Reding <[email protected]>
> +
> +properties:
> + compatible:
> + enum:
> + - nvidia,tegra20-core-domain
> + - nvidia,tegra30-core-domain

The file should be in bindings/power.
Include also the power-domain.yaml schema.

> +
> + operating-points-v2:
> + description:
> + Should contain level, voltages and opp-supported-hw property.
> + The supported-hw is a bitfield indicating SoC speedo or process
> + ID mask.
> +
> + "#power-domain-cells":
> + const: 0
> +
> + power-supply:
> + description:
> + Phandle to voltage regulator connected to the SoC Core power rail.
> +
> +required:
> + - compatible
> + - operating-points-v2
> + - "#power-domain-cells"
> + - power-supply
> +
> +additionalProperties: false
> +
> +examples:
> + - |
> + core-domain {

power-domain (to follow schema and devicetree spec)

Best regards,
Krzysztof


> + compatible = "nvidia,tegra20-core-domain";
> + operating-points-v2 = <&opp_table>;
> + power-supply = <&regulator>;
> + #power-domain-cells = <0>;
> + };
> --
> 2.29.2
>

2020-12-19 11:04:52

by Krzysztof Kozlowski

[permalink] [raw]
Subject: Re: [PATCH v2 41/48] memory: tegra20-emc: Use devm_tegra_core_dev_init_opp_table()

On Thu, Dec 17, 2020 at 09:06:31PM +0300, Dmitry Osipenko wrote:
> Use common devm_tegra_core_dev_init_opp_table() helper for the OPP table
> initialization.
>
> Signed-off-by: Dmitry Osipenko <[email protected]>
> ---
> drivers/memory/tegra/tegra20-emc.c | 57 +++---------------------------
> 1 file changed, 4 insertions(+), 53 deletions(-)

If there was no more Tegra MC work planned, this could easily go via
Tegra SoC tree. However I expect still work of your interconnect
patches, so maybe it's better to stick these in same tree.

In such case I would need a stable tag with the
devm_tegra_core_dev_init_opp_table() helper for memory controller tree.

Best regards,
Krzysztof

2020-12-20 18:28:26

by Dmitry Osipenko

[permalink] [raw]
Subject: Re: [PATCH v2 07/48] dt-bindings: arm: tegra: Add binding for core power domain

19.12.2020 13:57, Krzysztof Kozlowski пишет:
> On Thu, Dec 17, 2020 at 09:05:57PM +0300, Dmitry Osipenko wrote:
>> All NVIDIA Tegra SoCs have a core power domain where majority of hardware
>> blocks reside. Add binding for the core power domain.
>>
>> Signed-off-by: Dmitry Osipenko <[email protected]>
>> ---
>> .../arm/tegra/nvidia,tegra20-core-domain.yaml | 48 +++++++++++++++++++
>> 1 file changed, 48 insertions(+)
>> create mode 100644 Documentation/devicetree/bindings/arm/tegra/nvidia,tegra20-core-domain.yaml
>>
>> diff --git a/Documentation/devicetree/bindings/arm/tegra/nvidia,tegra20-core-domain.yaml b/Documentation/devicetree/bindings/arm/tegra/nvidia,tegra20-core-domain.yaml
>> new file mode 100644
>> index 000000000000..f3d8fd2d8371
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/arm/tegra/nvidia,tegra20-core-domain.yaml
>> @@ -0,0 +1,48 @@
>> +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
>> +%YAML 1.2
>> +---
>> +$id: http://devicetree.org/schemas/arm/tegra/nvidia,tegra20-core-domain.yaml#
>> +$schema: http://devicetree.org/meta-schemas/core.yaml#
>> +
>> +title: NVIDIA Tegra Core Power Domain
>> +
>> +maintainers:
>> + - Dmitry Osipenko <[email protected]>
>> + - Jon Hunter <[email protected]>
>> + - Thierry Reding <[email protected]>
>> +
>> +properties:
>> + compatible:
>> + enum:
>> + - nvidia,tegra20-core-domain
>> + - nvidia,tegra30-core-domain
>
> The file should be in bindings/power.
> Include also the power-domain.yaml schema.
>
>> +
>> + operating-points-v2:
>> + description:
>> + Should contain level, voltages and opp-supported-hw property.
>> + The supported-hw is a bitfield indicating SoC speedo or process
>> + ID mask.
>> +
>> + "#power-domain-cells":
>> + const: 0
>> +
>> + power-supply:
>> + description:
>> + Phandle to voltage regulator connected to the SoC Core power rail.
>> +
>> +required:
>> + - compatible
>> + - operating-points-v2
>> + - "#power-domain-cells"
>> + - power-supply
>> +
>> +additionalProperties: false
>> +
>> +examples:
>> + - |
>> + core-domain {
>
> power-domain (to follow schema and devicetree spec)

Thanks for the suggestion, I'll update it in v3.

2020-12-20 18:37:08

by Dmitry Osipenko

[permalink] [raw]
Subject: Re: [PATCH v2 41/48] memory: tegra20-emc: Use devm_tegra_core_dev_init_opp_table()

19.12.2020 14:02, Krzysztof Kozlowski пишет:
> On Thu, Dec 17, 2020 at 09:06:31PM +0300, Dmitry Osipenko wrote:
>> Use common devm_tegra_core_dev_init_opp_table() helper for the OPP table
>> initialization.
>>
>> Signed-off-by: Dmitry Osipenko <[email protected]>
>> ---
>> drivers/memory/tegra/tegra20-emc.c | 57 +++---------------------------
>> 1 file changed, 4 insertions(+), 53 deletions(-)
>
> If there was no more Tegra MC work planned, this could easily go via
> Tegra SoC tree. However I expect still work of your interconnect
> patches, so maybe it's better to stick these in same tree.

I'll re-send the remaining interconnect patches soon.

> In such case I would need a stable tag with the
> devm_tegra_core_dev_init_opp_table() helper for memory controller tree.

Perhaps will be better to drop these memory changes for now from this
series since they are optional, i.e. memory drivers will work properly
because voltage changes are done by the OPP core and these patches just
replace the duplicated code with a new common helper which doesn't add
new features to the memory drivers. It should be fine to get back to
these memory patches once interconnect patchset will be fully merged.
I'll take it into account in v3, thanks.

2020-12-22 00:10:42

by Rob Herring (Arm)

[permalink] [raw]
Subject: Re: [PATCH v2 04/48] dt-bindings: host1x: Document OPP and power domain properties

On Thu, 17 Dec 2020 21:05:54 +0300, Dmitry Osipenko wrote:
> Document new DVFS OPP table and power domain properties of the Host1x bus
> and devices sitting on the bus.
>
> Signed-off-by: Dmitry Osipenko <[email protected]>
> ---
> .../display/tegra/nvidia,tegra20-host1x.txt | 49 +++++++++++++++++++
> 1 file changed, 49 insertions(+)
>

Reviewed-by: Rob Herring <[email protected]>

2020-12-22 00:12:23

by Rob Herring (Arm)

[permalink] [raw]
Subject: Re: [PATCH v2 05/48] media: dt: bindings: tegra-vde: Document OPP and power domain properties

On Thu, 17 Dec 2020 21:05:55 +0300, Dmitry Osipenko wrote:
> Document new DVFS OPP table and power domain properties of the video
> decoder engine.
>
> Signed-off-by: Dmitry Osipenko <[email protected]>
> ---
> .../devicetree/bindings/media/nvidia,tegra-vde.txt | 12 ++++++++++++
> 1 file changed, 12 insertions(+)
>

Reviewed-by: Rob Herring <[email protected]>

2020-12-22 00:18:20

by Rob Herring (Arm)

[permalink] [raw]
Subject: Re: [PATCH v2 06/48] dt-bindings: clock: tegra: Document clocks sub-node

On Thu, Dec 17, 2020 at 09:05:56PM +0300, Dmitry Osipenko wrote:
> Document "clocks" sub-node which describes Tegra SoC clocks that require
> a higher voltage of the core power domain in order to operate properly on
> a higher rates.

Seems like an odd reason to have a bunch of child nodes. It very much
seems like a problem you'd need to solve after you design the binding
which should be fixed within the kernel.

This is also above my threshold for 'convert to schema' first...

>
> Signed-off-by: Dmitry Osipenko <[email protected]>
> ---
> .../bindings/clock/nvidia,tegra20-car.txt | 26 +++++++++++++++++++
> .../bindings/clock/nvidia,tegra30-car.txt | 26 +++++++++++++++++++
> 2 files changed, 52 insertions(+)
>
> diff --git a/Documentation/devicetree/bindings/clock/nvidia,tegra20-car.txt b/Documentation/devicetree/bindings/clock/nvidia,tegra20-car.txt
> index 6c5901b503d0..353354477785 100644
> --- a/Documentation/devicetree/bindings/clock/nvidia,tegra20-car.txt
> +++ b/Documentation/devicetree/bindings/clock/nvidia,tegra20-car.txt
> @@ -19,6 +19,16 @@ Required properties :
> In clock consumers, this cell represents the bit number in the CAR's
> array of CLK_RST_CONTROLLER_RST_DEVICES_* registers.
>
> +Optional child sub-node "clocks" should contain nodes matching the clocks
> +on the Tegra SoC. Refer to Tegra TRM for mode details on the clock nodes.
> +
> +Required properties :
> +- compatible : Should be "nvidia,tegra20-clock".
> +- operating-points-v2: See ../bindings/opp/opp.txt for details.
> +- clocks : Should contain clock which corresponds to the node.
> +- power-domains: Phandle to a power domain node as described by generic
> + PM domain bindings.
> +
> Example SoC include file:
>
> / {
> @@ -27,6 +37,22 @@ Example SoC include file:
> reg = <0x60006000 0x1000>;
> #clock-cells = <1>;
> #reset-cells = <1>;
> +
> + clocks {
> + hdmi {
> + compatible = "nvidia,tegra20-clock";
> + operating-points-v2 = <&hdmi_opp_table>;
> + clocks = <&tegra_car TEGRA20_CLK_HDMI>;
> + power-domains = <&domain>;
> + };
> +
> + pll_m {
> + compatible = "nvidia,tegra20-clock";
> + operating-points-v2 = <&pll_m_opp_table>;
> + clocks = <&tegra_car TEGRA20_CLK_PLL_M>;
> + power-domains = <&domain>;
> + };
> + };
> };
>
> usb@c5004000 {
> diff --git a/Documentation/devicetree/bindings/clock/nvidia,tegra30-car.txt b/Documentation/devicetree/bindings/clock/nvidia,tegra30-car.txt
> index 63618cde12df..bc7bbdaa9d3f 100644
> --- a/Documentation/devicetree/bindings/clock/nvidia,tegra30-car.txt
> +++ b/Documentation/devicetree/bindings/clock/nvidia,tegra30-car.txt
> @@ -19,6 +19,16 @@ Required properties :
> In clock consumers, this cell represents the bit number in the CAR's
> array of CLK_RST_CONTROLLER_RST_DEVICES_* registers.
>
> +Optional child sub-node "clocks" should contain nodes matching the clocks
> +on the Tegra SoC. Refer to Tegra TRM for mode details on the clock nodes.
> +
> +Required properties :
> +- compatible : Should be "nvidia,tegra30-clock".
> +- operating-points-v2: See ../bindings/opp/opp.txt for details.
> +- clocks : Should contain clock which corresponds to the node.
> +- power-domains: Phandle to a power domain node as described by generic
> + PM domain bindings.
> +
> Example SoC include file:
>
> / {
> @@ -31,6 +41,22 @@ Example SoC include file:
>
> usb@c5004000 {
> clocks = <&tegra_car TEGRA30_CLK_USB2>;
> +
> + clocks {
> + hdmi {
> + compatible = "nvidia,tegra30-clock";
> + operating-points-v2 = <&hdmi_opp_table>;
> + clocks = <&tegra_car TEGRA30_CLK_HDMI>;
> + power-domains = <&domain>;
> + };
> +
> + pll_m {
> + compatible = "nvidia,tegra30-clock";
> + operating-points-v2 = <&pll_m_opp_table>;
> + clocks = <&tegra_car TEGRA30_CLK_PLL_M>;
> + power-domains = <&domain>;
> + };
> + };
> };
> };
>
> --
> 2.29.2
>

2020-12-22 05:49:59

by Viresh Kumar

[permalink] [raw]
Subject: Re: [PATCH v2 43/48] ARM: tegra: Add OPP tables and power domains to Tegra20 device-tree

On 17-12-20, 21:06, Dmitry Osipenko wrote:
> diff --git a/arch/arm/boot/dts/tegra20-peripherals-opp.dtsi b/arch/arm/boot/dts/tegra20-peripherals-opp.dtsi
> index b84afecea154..7e015cdfbc55 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>;
> + };

I am not sure I fully understand this, why does it have both microvolt and level
properties ?

--
viresh

2020-12-22 06:42:18

by Viresh Kumar

[permalink] [raw]
Subject: Re: [PATCH v2 28/48] soc/tegra: Introduce core power domain driver

On 17-12-20, 21:06, Dmitry Osipenko wrote:
> +++ b/drivers/soc/tegra/core-power-domain.c
> @@ -0,0 +1,125 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * NVIDIA Tegra SoC Core Power Domain Driver
> + */
> +
> +#include <linux/of_device.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_domain.h>
> +#include <linux/pm_opp.h>
> +#include <linux/slab.h>
> +
> +#include <soc/tegra/common.h>
> +
> +static struct lock_class_key tegra_core_domain_lock_class;
> +static bool tegra_core_domain_state_synced;
> +
> +static int tegra_genpd_set_performance_state(struct generic_pm_domain *genpd,
> + unsigned int level)
> +{
> + struct dev_pm_opp *opp;
> + int err;
> +
> + opp = dev_pm_opp_find_level_ceil(&genpd->dev, &level);

We don't need ceil or floor versions for level, but rather _exact() version. Or
maybe just call it dev_pm_opp_find_level().

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

IIUC, you implemented this callback because you want to use the voltage triplet
present in the OPP table ?

And so you are setting the regulator ("power") later in this patch ?

I am not in favor of implementing this routine, as it just adds a wrapper above
the regulator API. What you should be doing rather is get the regulator by
yourself here (instead of depending on the OPP core). And then you can do
dev_pm_opp_get_voltage() here and set the voltage yourself. You may want to
implement a version supporting triplet here though for the same.

And you won't require the sync version of the API as well then.

--
viresh

2020-12-22 06:43:58

by Viresh Kumar

[permalink] [raw]
Subject: Re: [PATCH v2 09/48] opp: Add dev_pm_opp_sync_regulators()

On 17-12-20, 21:05, Dmitry Osipenko wrote:
> Extend OPP API with dev_pm_opp_sync_regulators() function, which syncs
> voltage state of regulators.
>
> Signed-off-by: Dmitry Osipenko <[email protected]>

We shouldn't be doing this, details in patch 28.

--
viresh

2020-12-22 06:44:41

by Viresh Kumar

[permalink] [raw]
Subject: Re: [PATCH v2 10/48] opp: Add dev_pm_opp_set_voltage()

On 17-12-20, 21:06, Dmitry Osipenko wrote:
> Add dev_pm_opp_set_voltage() which allows OPP table users to set voltage
> in accordance to a given OPP. In particular this is needed for driving
> voltage of a generic power domain which uses OPPs and doesn't have a
> clock.
>
> Signed-off-by: Dmitry Osipenko <[email protected]>

We shouldn't be doing this, details in patch 28.

--
viresh

2020-12-22 06:45:30

by Viresh Kumar

[permalink] [raw]
Subject: Re: [PATCH v2 11/48] opp: Add dev_pm_opp_find_level_ceil()

On 17-12-20, 21:06, Dmitry Osipenko wrote:
> Add a ceil version of the dev_pm_opp_find_level(). It's handy to have if
> levels don't start from 0 in OPP table and zero usually means a minimal
> level.
>
> Signed-off-by: Dmitry Osipenko <[email protected]>

Why doesn't the exact version work for you here ?

--
viresh

2020-12-22 08:57:43

by Viresh Kumar

[permalink] [raw]
Subject: Re: [PATCH v2 13/48] opp: Add resource-managed versions of OPP API functions

On 17-12-20, 21:06, Dmitry Osipenko wrote:
> Add resource-managed versions of OPP API functions. This removes a need
> from drivers to store and manage OPP table pointers.
>
> Signed-off-by: Dmitry Osipenko <[email protected]>
> ---
> drivers/opp/core.c | 173 +++++++++++++++++++++++++++++++++++++++++
> drivers/opp/of.c | 25 ++++++
> include/linux/pm_opp.h | 51 ++++++++++++
> 3 files changed, 249 insertions(+)

Please send a patchset of its own for this patch, along with updates to all the
existing code that can make use of these.

--
viresh

2020-12-22 09:01:11

by Viresh Kumar

[permalink] [raw]
Subject: Re: [PATCH v2 14/48] opp: Filter out OPPs based on availability of a required-OPP

On 17-12-20, 21:06, Dmitry Osipenko wrote:
> A required OPP may not be available, and thus, all OPPs which are using
> this required OPP should be unavailable too.
>
> Signed-off-by: Dmitry Osipenko <[email protected]>
> ---
> drivers/opp/core.c | 11 ++++++++++-
> 1 file changed, 10 insertions(+), 1 deletion(-)

Please send a separate patchset for fixes, as these can also go to 5.11 itself.

> diff --git a/drivers/opp/core.c b/drivers/opp/core.c
> index d9feb7639598..3d02fe33630b 100644
> --- a/drivers/opp/core.c
> +++ b/drivers/opp/core.c
> @@ -1588,7 +1588,7 @@ int _opp_add(struct device *dev, struct dev_pm_opp *new_opp,
> struct opp_table *opp_table, bool rate_not_available)
> {
> struct list_head *head;
> - int ret;
> + int i, ret;
>
> mutex_lock(&opp_table->lock);
> head = &opp_table->opp_list;
> @@ -1615,6 +1615,15 @@ int _opp_add(struct device *dev, struct dev_pm_opp *new_opp,
> __func__, new_opp->rate);
> }
>
> + for (i = 0; i < opp_table->required_opp_count && new_opp->available; i++) {
> + if (new_opp->required_opps[i]->available)
> + continue;
> +
> + new_opp->available = false;
> + dev_warn(dev, "%s: OPP not supported by required OPP %pOF (%lu)\n",
> + __func__, new_opp->required_opps[i]->np, new_opp->rate);

Why not just break from here ?

> + }
> +
> return 0;
> }
>
> --
> 2.29.2

--
viresh

2020-12-22 09:03:55

by Viresh Kumar

[permalink] [raw]
Subject: Re: [PATCH v2 15/48] opp: Support set_opp() customization without requiring to use regulators

On 17-12-20, 21:06, Dmitry Osipenko wrote:
> Support set_opp() customization without requiring to use regulators. This
> is needed by drivers which want to use dev_pm_opp_set_rate() for changing
> rates of a multiple clocks and don't need to touch regulator.
>
> One example is NVIDIA Tegra30/114 SoCs which have two sibling 3D hardware
> units which should be use to the same clock rate, meanwhile voltage
> scaling is done using a power domain. In this case OPP table doesn't have
> a regulator, causing a NULL dereference in _set_opp_custom().
>
> Signed-off-by: Dmitry Osipenko <[email protected]>
> ---
> drivers/opp/core.c | 16 ++++++++++++----
> 1 file changed, 12 insertions(+), 4 deletions(-)
>
> diff --git a/drivers/opp/core.c b/drivers/opp/core.c
> index 3d02fe33630b..625dae7a5ecb 100644
> --- a/drivers/opp/core.c
> +++ b/drivers/opp/core.c
> @@ -828,17 +828,25 @@ static int _set_opp_custom(const struct opp_table *opp_table,
> struct dev_pm_opp_supply *old_supply,
> struct dev_pm_opp_supply *new_supply)
> {
> - struct dev_pm_set_opp_data *data;
> + struct dev_pm_set_opp_data *data, tmp_data;
> + unsigned int regulator_count;
> int size;
>
> - data = opp_table->set_opp_data;
> + if (opp_table->set_opp_data) {
> + data = opp_table->set_opp_data;
> + regulator_count = opp_table->regulator_count;
> + } else {
> + data = &tmp_data;
> + regulator_count = 0;
> + }
> +
> data->regulators = opp_table->regulators;
> - data->regulator_count = opp_table->regulator_count;
> + data->regulator_count = regulator_count;
> data->clk = opp_table->clk;
> data->dev = dev;
>
> data->old_opp.rate = old_freq;
> - size = sizeof(*old_supply) * opp_table->regulator_count;
> + size = sizeof(*old_supply) * regulator_count;
> if (!old_supply)
> memset(data->old_opp.supplies, 0, size);
> else

I don't see you making use of this in this patchset. How did you get this to
crash ?

--
viresh

2020-12-22 09:16:15

by Viresh Kumar

[permalink] [raw]
Subject: Re: [PATCH v2 19/48] opp: Fix adding OPP entries in a wrong order if rate is unavailable

On 17-12-20, 21:06, Dmitry Osipenko wrote:
> Fix adding OPP entries in a wrong (opposite) order if OPP rate is
> unavailable. The OPP comparison is erroneously skipped if OPP rate is
> missing, thus OPPs are left unsorted.
>
> Signed-off-by: Dmitry Osipenko <[email protected]>
> ---
> drivers/opp/core.c | 23 ++++++++++++-----------
> drivers/opp/opp.h | 2 +-
> 2 files changed, 13 insertions(+), 12 deletions(-)
>
> diff --git a/drivers/opp/core.c b/drivers/opp/core.c
> index 34f7e530d941..5c7f130a8de2 100644
> --- a/drivers/opp/core.c
> +++ b/drivers/opp/core.c
> @@ -1531,9 +1531,10 @@ static bool _opp_supported_by_regulators(struct dev_pm_opp *opp,
> return true;
> }
>
> -int _opp_compare_key(struct dev_pm_opp *opp1, struct dev_pm_opp *opp2)
> +int _opp_compare_key(struct dev_pm_opp *opp1, struct dev_pm_opp *opp2,
> + bool rate_not_available)
> {
> - if (opp1->rate != opp2->rate)
> + if (!rate_not_available && opp1->rate != opp2->rate)

rate will be 0 for both the OPPs here if rate_not_available is true and so this
change shouldn't be required.

> return opp1->rate < opp2->rate ? -1 : 1;
> if (opp1->bandwidth && opp2->bandwidth &&
> opp1->bandwidth[0].peak != opp2->bandwidth[0].peak)
> @@ -1545,7 +1546,8 @@ int _opp_compare_key(struct dev_pm_opp *opp1, struct dev_pm_opp *opp2)
>
> static int _opp_is_duplicate(struct device *dev, struct dev_pm_opp *new_opp,
> struct opp_table *opp_table,
> - struct list_head **head)
> + struct list_head **head,
> + bool rate_not_available)
> {
> struct dev_pm_opp *opp;
> int opp_cmp;
> @@ -1559,13 +1561,13 @@ static int _opp_is_duplicate(struct device *dev, struct dev_pm_opp *new_opp,
> * loop.
> */
> list_for_each_entry(opp, &opp_table->opp_list, node) {
> - opp_cmp = _opp_compare_key(new_opp, opp);
> + opp_cmp = _opp_compare_key(new_opp, opp, rate_not_available);
> if (opp_cmp > 0) {
> *head = &opp->node;
> continue;
> }
>
> - if (opp_cmp < 0)
> + if (opp_cmp < 0 || rate_not_available)
> return 0;

This shouldn't be required as well, isn't it ?

>
> /* Duplicate OPPs */
> @@ -1601,12 +1603,11 @@ int _opp_add(struct device *dev, struct dev_pm_opp *new_opp,
> mutex_lock(&opp_table->lock);
> head = &opp_table->opp_list;
>
> - if (likely(!rate_not_available)) {
> - ret = _opp_is_duplicate(dev, new_opp, opp_table, &head);
> - if (ret) {
> - mutex_unlock(&opp_table->lock);
> - return ret;
> - }
> + ret = _opp_is_duplicate(dev, new_opp, opp_table, &head,
> + rate_not_available);

This is the only thing we need to do here I believe.

> + if (ret) {
> + mutex_unlock(&opp_table->lock);
> + return ret;
> }
>
> list_add(&new_opp->node, head);
> diff --git a/drivers/opp/opp.h b/drivers/opp/opp.h
> index 4ced7ffa8158..6f5be6c72f13 100644
> --- a/drivers/opp/opp.h
> +++ b/drivers/opp/opp.h
> @@ -219,7 +219,7 @@ struct opp_table *_find_opp_table(struct device *dev);
> struct opp_device *_add_opp_dev(const struct device *dev, struct opp_table *opp_table);
> struct dev_pm_opp *_opp_allocate(struct opp_table *opp_table);
> void _opp_free(struct dev_pm_opp *opp);
> -int _opp_compare_key(struct dev_pm_opp *opp1, struct dev_pm_opp *opp2);
> +int _opp_compare_key(struct dev_pm_opp *opp1, struct dev_pm_opp *opp2, bool rate_not_available);
> int _opp_add(struct device *dev, struct dev_pm_opp *new_opp, struct opp_table *opp_table, bool rate_not_available);
> int _opp_add_v1(struct opp_table *opp_table, struct device *dev, unsigned long freq, long u_volt, bool dynamic);
> void _dev_pm_opp_cpumask_remove_table(const struct cpumask *cpumask, int last_cpu);
> --
> 2.29.2

--
viresh

2020-12-22 09:16:27

by Viresh Kumar

[permalink] [raw]
Subject: Re: [PATCH v2 44/48] ARM: tegra: Add OPP tables and power domains to Tegra30 device-tree

On 17-12-20, 21:06, Dmitry Osipenko wrote:
> diff --git a/arch/arm/boot/dts/tegra30-peripherals-opp.dtsi b/arch/arm/boot/dts/tegra30-peripherals-opp.dtsi
> index cbe84d25e726..983db1a06682 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>;

Perhaps you don't need to exactly copy the voltage value into the level field.
The level field can just be kept to 0, 1,2, 3, etc..

> + };
> +
> + 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>;
> + };
> + };

--
viresh

2020-12-22 09:18:13

by Viresh Kumar

[permalink] [raw]
Subject: Re: [PATCH v2 00/48] Introduce core voltage scaling for NVIDIA Tegra20/30 SoCs

On 18-12-20, 16:51, Dmitry Osipenko wrote:
> Alright, although I haven't pretended that v2 patches should be merged
> right away since they are fundamentally different from v1, and thus, all
> patches need to be reviewed first.

I agree. I have done some basic review for the stuff.

> If the current OPP changes look good to you, then please give yours r-b
> to the patches. Thanks in advance!

r-b-y isn't required as they will go through my tree itself. So if everyone is
happy with the idea, please submit the patches separately (fixes, improvements,
devm_*, etc).

--
viresh

2020-12-22 19:16:41

by Dmitry Osipenko

[permalink] [raw]
Subject: Re: [PATCH v2 13/48] opp: Add resource-managed versions of OPP API functions

22.12.2020 11:55, Viresh Kumar пишет:
> On 17-12-20, 21:06, Dmitry Osipenko wrote:
>> Add resource-managed versions of OPP API functions. This removes a need
>> from drivers to store and manage OPP table pointers.
>>
>> Signed-off-by: Dmitry Osipenko <[email protected]>
>> ---
>> drivers/opp/core.c | 173 +++++++++++++++++++++++++++++++++++++++++
>> drivers/opp/of.c | 25 ++++++
>> include/linux/pm_opp.h | 51 ++++++++++++
>> 3 files changed, 249 insertions(+)
>
> Please send a patchset of its own for this patch, along with updates to all the
> existing code that can make use of these.
>

ok

2020-12-22 19:18:42

by Dmitry Osipenko

[permalink] [raw]
Subject: Re: [PATCH v2 11/48] opp: Add dev_pm_opp_find_level_ceil()

22.12.2020 09:42, Viresh Kumar пишет:
> On 17-12-20, 21:06, Dmitry Osipenko wrote:
>> Add a ceil version of the dev_pm_opp_find_level(). It's handy to have if
>> levels don't start from 0 in OPP table and zero usually means a minimal
>> level.
>>
>> Signed-off-by: Dmitry Osipenko <[email protected]>
>
> Why doesn't the exact version work for you here ?
>

The exact version won't find OPP for level=0 if levels don't start with
0, where 0 means that minimal level is desired.

2020-12-22 19:18:56

by Dmitry Osipenko

[permalink] [raw]
Subject: Re: [PATCH v2 06/48] dt-bindings: clock: tegra: Document clocks sub-node

22.12.2020 03:14, Rob Herring пишет:
> On Thu, Dec 17, 2020 at 09:05:56PM +0300, Dmitry Osipenko wrote:
>> Document "clocks" sub-node which describes Tegra SoC clocks that require
>> a higher voltage of the core power domain in order to operate properly on
>> a higher rates.
>
> Seems like an odd reason to have a bunch of child nodes. It very much
> seems like a problem you'd need to solve after you design the binding
> which should be fixed within the kernel.

The reason is that multiple root SoC PLL clocks need to have individual
OPP table because proper voltage should be maintained for these clocks
based on the clock rate.

Some of the clocks do not belong to any specific device and there is a
need to specify the OPP table for them. Each child node represents an
individual clock with the clock's OPP table and power domain.

Some clocks belong to a specific device, but don't require high voltages
and it's very convenient that clk device could manage voltage for these
clocks, instead of populating each device with OPP support (PWM, MMC and
etc).

I'll update the commit message with a better explanation in v3.

> This is also above my threshold for 'convert to schema' first...

Alright, will convert it in v3.

2020-12-22 19:19:17

by Dmitry Osipenko

[permalink] [raw]
Subject: Re: [PATCH v2 14/48] opp: Filter out OPPs based on availability of a required-OPP

22.12.2020 11:59, Viresh Kumar пишет:
> On 17-12-20, 21:06, Dmitry Osipenko wrote:
>> A required OPP may not be available, and thus, all OPPs which are using
>> this required OPP should be unavailable too.
>>
>> Signed-off-by: Dmitry Osipenko <[email protected]>
>> ---
>> drivers/opp/core.c | 11 ++++++++++-
>> 1 file changed, 10 insertions(+), 1 deletion(-)
>
> Please send a separate patchset for fixes, as these can also go to 5.11 itself.

Alright, although I don't think that this patch fixes any problems for
existing OPP users.

>> diff --git a/drivers/opp/core.c b/drivers/opp/core.c
>> index d9feb7639598..3d02fe33630b 100644
>> --- a/drivers/opp/core.c
>> +++ b/drivers/opp/core.c
>> @@ -1588,7 +1588,7 @@ int _opp_add(struct device *dev, struct dev_pm_opp *new_opp,
>> struct opp_table *opp_table, bool rate_not_available)
>> {
>> struct list_head *head;
>> - int ret;
>> + int i, ret;
>>
>> mutex_lock(&opp_table->lock);
>> head = &opp_table->opp_list;
>> @@ -1615,6 +1615,15 @@ int _opp_add(struct device *dev, struct dev_pm_opp *new_opp,
>> __func__, new_opp->rate);
>> }
>>
>> + for (i = 0; i < opp_table->required_opp_count && new_opp->available; i++) {
>> + if (new_opp->required_opps[i]->available)
>> + continue;
>> +
>> + new_opp->available = false;
>> + dev_warn(dev, "%s: OPP not supported by required OPP %pOF (%lu)\n",
>> + __func__, new_opp->required_opps[i]->np, new_opp->rate);
>
> Why not just break from here ?

The new_opp could be already marked as unavailable by a previous voltage
check, hence this loop should be skipped entirely in that case.

2020-12-22 19:19:19

by Dmitry Osipenko

[permalink] [raw]
Subject: Re: [PATCH v2 00/48] Introduce core voltage scaling for NVIDIA Tegra20/30 SoCs

22.12.2020 12:15, Viresh Kumar пишет:
> On 18-12-20, 16:51, Dmitry Osipenko wrote:
>> Alright, although I haven't pretended that v2 patches should be merged
>> right away since they are fundamentally different from v1, and thus, all
>> patches need to be reviewed first.
>
> I agree. I have done some basic review for the stuff.

Thank you for the review.

>> If the current OPP changes look good to you, then please give yours r-b
>> to the patches. Thanks in advance!
>
> r-b-y isn't required as they will go through my tree itself. So if everyone is
> happy with the idea, please submit the patches separately (fixes, improvements,
> devm_*, etc).

okay

2020-12-22 19:19:54

by Dmitry Osipenko

[permalink] [raw]
Subject: Re: [PATCH v2 15/48] opp: Support set_opp() customization without requiring to use regulators

22.12.2020 12:01, Viresh Kumar пишет:
> On 17-12-20, 21:06, Dmitry Osipenko wrote:
>> Support set_opp() customization without requiring to use regulators. This
>> is needed by drivers which want to use dev_pm_opp_set_rate() for changing
>> rates of a multiple clocks and don't need to touch regulator.
>>
>> One example is NVIDIA Tegra30/114 SoCs which have two sibling 3D hardware
>> units which should be use to the same clock rate, meanwhile voltage
>> scaling is done using a power domain. In this case OPP table doesn't have
>> a regulator, causing a NULL dereference in _set_opp_custom().
>>
>> Signed-off-by: Dmitry Osipenko <[email protected]>
>> ---
>> drivers/opp/core.c | 16 ++++++++++++----
>> 1 file changed, 12 insertions(+), 4 deletions(-)
>>
>> diff --git a/drivers/opp/core.c b/drivers/opp/core.c
>> index 3d02fe33630b..625dae7a5ecb 100644
>> --- a/drivers/opp/core.c
>> +++ b/drivers/opp/core.c
>> @@ -828,17 +828,25 @@ static int _set_opp_custom(const struct opp_table *opp_table,
>> struct dev_pm_opp_supply *old_supply,
>> struct dev_pm_opp_supply *new_supply)
>> {
>> - struct dev_pm_set_opp_data *data;
>> + struct dev_pm_set_opp_data *data, tmp_data;
>> + unsigned int regulator_count;
>> int size;
>>
>> - data = opp_table->set_opp_data;
>> + if (opp_table->set_opp_data) {
>> + data = opp_table->set_opp_data;
>> + regulator_count = opp_table->regulator_count;
>> + } else {
>> + data = &tmp_data;
>> + regulator_count = 0;
>> + }
>> +
>> data->regulators = opp_table->regulators;
>> - data->regulator_count = opp_table->regulator_count;
>> + data->regulator_count = regulator_count;
>> data->clk = opp_table->clk;
>> data->dev = dev;
>>
>> data->old_opp.rate = old_freq;
>> - size = sizeof(*old_supply) * opp_table->regulator_count;
>> + size = sizeof(*old_supply) * regulator_count;
>> if (!old_supply)
>> memset(data->old_opp.supplies, 0, size);
>> else
>
> I don't see you making use of this in this patchset. How did you get this to
> crash ?
>

Please see patch 38 where 3d driver makes use of
devm_pm_opp_register_set_opp_helper().

The opp_table->set_opp_data=NULL and opp_table->regulator_count=-1 if
OPP table doesn't use a regulator, hence the crash happens.

2020-12-22 19:21:09

by Dmitry Osipenko

[permalink] [raw]
Subject: Re: [PATCH v2 19/48] opp: Fix adding OPP entries in a wrong order if rate is unavailable

22.12.2020 12:12, Viresh Kumar пишет:
> On 17-12-20, 21:06, Dmitry Osipenko wrote:
>> Fix adding OPP entries in a wrong (opposite) order if OPP rate is
>> unavailable. The OPP comparison is erroneously skipped if OPP rate is
>> missing, thus OPPs are left unsorted.
>>
>> Signed-off-by: Dmitry Osipenko <[email protected]>
>> ---
>> drivers/opp/core.c | 23 ++++++++++++-----------
>> drivers/opp/opp.h | 2 +-
>> 2 files changed, 13 insertions(+), 12 deletions(-)
>>
>> diff --git a/drivers/opp/core.c b/drivers/opp/core.c
>> index 34f7e530d941..5c7f130a8de2 100644
>> --- a/drivers/opp/core.c
>> +++ b/drivers/opp/core.c
>> @@ -1531,9 +1531,10 @@ static bool _opp_supported_by_regulators(struct dev_pm_opp *opp,
>> return true;
>> }
>>
>> -int _opp_compare_key(struct dev_pm_opp *opp1, struct dev_pm_opp *opp2)
>> +int _opp_compare_key(struct dev_pm_opp *opp1, struct dev_pm_opp *opp2,
>> + bool rate_not_available)
>> {
>> - if (opp1->rate != opp2->rate)
>> + if (!rate_not_available && opp1->rate != opp2->rate)
>
> rate will be 0 for both the OPPs here if rate_not_available is true and so this
> change shouldn't be required.

The rate_not_available is negated in the condition. This change is
required because both rates are 0 and then we should proceed to the
levels comparison.

I guess it's not clear by looking at this patch, please see a full
version of the function:

int _opp_compare_key(struct dev_pm_opp *opp1, struct dev_pm_opp *opp2,
bool rate_not_available)
{
if (!rate_not_available && opp1->rate != opp2->rate)
return opp1->rate < opp2->rate ? -1 : 1;
if (opp1->bandwidth && opp2->bandwidth &&
opp1->bandwidth[0].peak != opp2->bandwidth[0].peak)
return opp1->bandwidth[0].peak < opp2->bandwidth[0].peak ? -1 : 1;
if (opp1->level != opp2->level)
return opp1->level < opp2->level ? -1 : 1;
return 0;
}

Perhaps we could check whether opp1->rate=0, like it's done for the
opp1->bandwidth. I'll consider this variant for v3, thanks.

2020-12-22 19:22:54

by Dmitry Osipenko

[permalink] [raw]
Subject: Re: [PATCH v2 28/48] soc/tegra: Introduce core power domain driver

22.12.2020 09:40, Viresh Kumar пишет:
> On 17-12-20, 21:06, Dmitry Osipenko wrote:
>> +++ b/drivers/soc/tegra/core-power-domain.c
>> @@ -0,0 +1,125 @@
>> +// SPDX-License-Identifier: GPL-2.0+
>> +/*
>> + * NVIDIA Tegra SoC Core Power Domain Driver
>> + */
>> +
>> +#include <linux/of_device.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/pm_domain.h>
>> +#include <linux/pm_opp.h>
>> +#include <linux/slab.h>
>> +
>> +#include <soc/tegra/common.h>
>> +
>> +static struct lock_class_key tegra_core_domain_lock_class;
>> +static bool tegra_core_domain_state_synced;
>> +
>> +static int tegra_genpd_set_performance_state(struct generic_pm_domain *genpd,
>> + unsigned int level)
>> +{
>> + struct dev_pm_opp *opp;
>> + int err;
>> +
>> + opp = dev_pm_opp_find_level_ceil(&genpd->dev, &level);
>
> We don't need ceil or floor versions for level, but rather _exact() version. Or
> maybe just call it dev_pm_opp_find_level().

The _exact() version won't find OPP for level=0 if levels don't start
with 0.

>> + if (IS_ERR(opp)) {
>> + dev_err(&genpd->dev, "failed to find OPP for level %u: %pe\n",
>> + level, opp);
>> + return PTR_ERR(opp);
>> + }
>> +
>> + err = dev_pm_opp_set_voltage(&genpd->dev, opp);
>
> IIUC, you implemented this callback because you want to use the voltage triplet
> present in the OPP table ?
>
> And so you are setting the regulator ("power") later in this patch ?

yes

> I am not in favor of implementing this routine, as it just adds a wrapper above
> the regulator API. What you should be doing rather is get the regulator by
> yourself here (instead of depending on the OPP core). And then you can do
> dev_pm_opp_get_voltage() here and set the voltage yourself. You may want to
> implement a version supporting triplet here though for the same.
>
> And you won't require the sync version of the API as well then.
>

That's what I initially did for this driver. I don't mind to revert back
to the initial variant in v3, it appeared to me that it will be nicer
and cleaner to have OPP API managing everything here.

2020-12-22 19:26:10

by Dmitry Osipenko

[permalink] [raw]
Subject: Re: [PATCH v2 43/48] ARM: tegra: Add OPP tables and power domains to Tegra20 device-tree

22.12.2020 08:47, Viresh Kumar пишет:
> On 17-12-20, 21:06, Dmitry Osipenko wrote:
>> diff --git a/arch/arm/boot/dts/tegra20-peripherals-opp.dtsi b/arch/arm/boot/dts/tegra20-peripherals-opp.dtsi
>> index b84afecea154..7e015cdfbc55 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>;
>> + };
>
> I am not sure I fully understand this, why does it have both microvolt and level
> properties ?
>

The level is used by everything related to GENPD, while voltage is used
by the domain driver.

I think it's cleaner to separate the level from voltage, even though
they are set to the same values, which is done purely for convenience.
The 0,1,2,3 levels are meaningless in the context of this power domain,
while voltages make total sense.

2020-12-22 19:27:44

by Dmitry Osipenko

[permalink] [raw]
Subject: Re: [PATCH v2 44/48] ARM: tegra: Add OPP tables and power domains to Tegra30 device-tree

22.12.2020 12:14, Viresh Kumar пишет:
> On 17-12-20, 21:06, Dmitry Osipenko wrote:
>> diff --git a/arch/arm/boot/dts/tegra30-peripherals-opp.dtsi b/arch/arm/boot/dts/tegra30-peripherals-opp.dtsi
>> index cbe84d25e726..983db1a06682 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>;
>
> Perhaps you don't need to exactly copy the voltage value into the level field.
> The level field can just be kept to 0, 1,2, 3, etc..

It's much more convenient to set both fields to the same value because
the level values are shown in GENPD debugfs and the 0,1,2 values are
meaningless from the debugging perspective.

2020-12-22 19:41:55

by Dmitry Osipenko

[permalink] [raw]
Subject: Re: [PATCH v2 28/48] soc/tegra: Introduce core power domain driver

22.12.2020 22:21, Dmitry Osipenko пишет:
>>> + if (IS_ERR(opp)) {
>>> + dev_err(&genpd->dev, "failed to find OPP for level %u: %pe\n",
>>> + level, opp);
>>> + return PTR_ERR(opp);
>>> + }
>>> +
>>> + err = dev_pm_opp_set_voltage(&genpd->dev, opp);
>> IIUC, you implemented this callback because you want to use the voltage triplet
>> present in the OPP table ?
>>
>> And so you are setting the regulator ("power") later in this patch ?
> yes
>
>> I am not in favor of implementing this routine, as it just adds a wrapper above
>> the regulator API. What you should be doing rather is get the regulator by
>> yourself here (instead of depending on the OPP core). And then you can do
>> dev_pm_opp_get_voltage() here and set the voltage yourself. You may want to
>> implement a version supporting triplet here though for the same.
>>
>> And you won't require the sync version of the API as well then.
>>
> That's what I initially did for this driver. I don't mind to revert back
> to the initial variant in v3, it appeared to me that it will be nicer
> and cleaner to have OPP API managing everything here.

I forgot one important detail (why the initial variant wasn't good)..
OPP entries that have unsupportable voltages should be filtered out and
OPP core performs the filtering only if regulator is assigned to the OPP
table.

If regulator is assigned to the OPP table, then we need to use OPP API
for driving the regulator, hence that's why I added
dev_pm_opp_sync_regulators() and dev_pm_opp_set_voltage().

Perhaps it should be possible to add dev_pm_opp_get_regulator() that
will return the OPP table regulator in order to allow driver to use the
regulator directly. But I'm not sure whether this is a much better
option than the opp_sync_regulators() and opp_set_voltage() APIs.

2020-12-23 04:21:29

by Viresh Kumar

[permalink] [raw]
Subject: Re: [PATCH v2 11/48] opp: Add dev_pm_opp_find_level_ceil()

On 22-12-20, 22:15, Dmitry Osipenko wrote:
> 22.12.2020 09:42, Viresh Kumar пишет:
> > On 17-12-20, 21:06, Dmitry Osipenko wrote:
> >> Add a ceil version of the dev_pm_opp_find_level(). It's handy to have if
> >> levels don't start from 0 in OPP table and zero usually means a minimal
> >> level.
> >>
> >> Signed-off-by: Dmitry Osipenko <[email protected]>
> >
> > Why doesn't the exact version work for you here ?
> >
>
> The exact version won't find OPP for level=0 if levels don't start with
> 0, where 0 means that minimal level is desired.

Right, but why do you need to send 0 for your platform ?

--
viresh

2020-12-23 04:23:54

by Viresh Kumar

[permalink] [raw]
Subject: Re: [PATCH v2 14/48] opp: Filter out OPPs based on availability of a required-OPP

On 22-12-20, 22:17, Dmitry Osipenko wrote:
> 22.12.2020 11:59, Viresh Kumar пишет:
> > On 17-12-20, 21:06, Dmitry Osipenko wrote:
> >> A required OPP may not be available, and thus, all OPPs which are using
> >> this required OPP should be unavailable too.
> >>
> >> Signed-off-by: Dmitry Osipenko <[email protected]>
> >> ---
> >> drivers/opp/core.c | 11 ++++++++++-
> >> 1 file changed, 10 insertions(+), 1 deletion(-)
> >
> > Please send a separate patchset for fixes, as these can also go to 5.11 itself.
>
> Alright, although I don't think that this patch fixes any problems for
> existing OPP users.

Because nobody is using this feature, but otherwise this is a fix for me.

> >> diff --git a/drivers/opp/core.c b/drivers/opp/core.c
> >> index d9feb7639598..3d02fe33630b 100644
> >> --- a/drivers/opp/core.c
> >> +++ b/drivers/opp/core.c
> >> @@ -1588,7 +1588,7 @@ int _opp_add(struct device *dev, struct dev_pm_opp *new_opp,
> >> struct opp_table *opp_table, bool rate_not_available)
> >> {
> >> struct list_head *head;
> >> - int ret;
> >> + int i, ret;
> >>
> >> mutex_lock(&opp_table->lock);
> >> head = &opp_table->opp_list;
> >> @@ -1615,6 +1615,15 @@ int _opp_add(struct device *dev, struct dev_pm_opp *new_opp,
> >> __func__, new_opp->rate);
> >> }
> >>
> >> + for (i = 0; i < opp_table->required_opp_count && new_opp->available; i++) {
> >> + if (new_opp->required_opps[i]->available)
> >> + continue;
> >> +
> >> + new_opp->available = false;
> >> + dev_warn(dev, "%s: OPP not supported by required OPP %pOF (%lu)\n",
> >> + __func__, new_opp->required_opps[i]->np, new_opp->rate);
> >
> > Why not just break from here ?
>
> The new_opp could be already marked as unavailable by a previous voltage
> check, hence this loop should be skipped entirely in that case.

Then add a separate check for that before the loop as we don't need that check
on every iteration here.

--
viresh

2020-12-23 04:37:49

by Viresh Kumar

[permalink] [raw]
Subject: Re: [PATCH v2 19/48] opp: Fix adding OPP entries in a wrong order if rate is unavailable

On 22-12-20, 22:19, Dmitry Osipenko wrote:
> 22.12.2020 12:12, Viresh Kumar пишет:
> > On 17-12-20, 21:06, Dmitry Osipenko wrote:
> >> Fix adding OPP entries in a wrong (opposite) order if OPP rate is
> >> unavailable. The OPP comparison is erroneously skipped if OPP rate is
> >> missing, thus OPPs are left unsorted.
> >>
> >> Signed-off-by: Dmitry Osipenko <[email protected]>
> >> ---
> >> drivers/opp/core.c | 23 ++++++++++++-----------
> >> drivers/opp/opp.h | 2 +-
> >> 2 files changed, 13 insertions(+), 12 deletions(-)
> >>
> >> diff --git a/drivers/opp/core.c b/drivers/opp/core.c
> >> index 34f7e530d941..5c7f130a8de2 100644
> >> --- a/drivers/opp/core.c
> >> +++ b/drivers/opp/core.c
> >> @@ -1531,9 +1531,10 @@ static bool _opp_supported_by_regulators(struct dev_pm_opp *opp,
> >> return true;
> >> }
> >>
> >> -int _opp_compare_key(struct dev_pm_opp *opp1, struct dev_pm_opp *opp2)
> >> +int _opp_compare_key(struct dev_pm_opp *opp1, struct dev_pm_opp *opp2,
> >> + bool rate_not_available)
> >> {
> >> - if (opp1->rate != opp2->rate)
> >> + if (!rate_not_available && opp1->rate != opp2->rate)
> >
> > rate will be 0 for both the OPPs here if rate_not_available is true and so this
> > change shouldn't be required.
>
> The rate_not_available is negated in the condition. This change is
> required because both rates are 0 and then we should proceed to the
> levels comparison.

Won't that happen without this patch ?

> I guess it's not clear by looking at this patch, please see a full
> version of the function:
>
> int _opp_compare_key(struct dev_pm_opp *opp1, struct dev_pm_opp *opp2,
> bool rate_not_available)
> {
> if (!rate_not_available && opp1->rate != opp2->rate)
> return opp1->rate < opp2->rate ? -1 : 1;
> if (opp1->bandwidth && opp2->bandwidth &&
> opp1->bandwidth[0].peak != opp2->bandwidth[0].peak)
> return opp1->bandwidth[0].peak < opp2->bandwidth[0].peak ? -1 : 1;
> if (opp1->level != opp2->level)
> return opp1->level < opp2->level ? -1 : 1;
> return 0;
> }
>
> Perhaps we could check whether opp1->rate=0, like it's done for the
> opp1->bandwidth. I'll consider this variant for v3, thanks.

--
viresh

2020-12-23 05:58:54

by Viresh Kumar

[permalink] [raw]
Subject: Re: [PATCH v2 28/48] soc/tegra: Introduce core power domain driver

On 22-12-20, 22:39, Dmitry Osipenko wrote:
> 22.12.2020 22:21, Dmitry Osipenko пишет:
> >>> + if (IS_ERR(opp)) {
> >>> + dev_err(&genpd->dev, "failed to find OPP for level %u: %pe\n",
> >>> + level, opp);
> >>> + return PTR_ERR(opp);
> >>> + }
> >>> +
> >>> + err = dev_pm_opp_set_voltage(&genpd->dev, opp);
> >> IIUC, you implemented this callback because you want to use the voltage triplet
> >> present in the OPP table ?
> >>
> >> And so you are setting the regulator ("power") later in this patch ?
> > yes
> >
> >> I am not in favor of implementing this routine, as it just adds a wrapper above
> >> the regulator API. What you should be doing rather is get the regulator by
> >> yourself here (instead of depending on the OPP core). And then you can do
> >> dev_pm_opp_get_voltage() here and set the voltage yourself. You may want to
> >> implement a version supporting triplet here though for the same.
> >>
> >> And you won't require the sync version of the API as well then.
> >>
> > That's what I initially did for this driver. I don't mind to revert back
> > to the initial variant in v3, it appeared to me that it will be nicer
> > and cleaner to have OPP API managing everything here.
>
> I forgot one important detail (why the initial variant wasn't good)..
> OPP entries that have unsupportable voltages should be filtered out and
> OPP core performs the filtering only if regulator is assigned to the OPP
> table.
>
> If regulator is assigned to the OPP table, then we need to use OPP API
> for driving the regulator, hence that's why I added
> dev_pm_opp_sync_regulators() and dev_pm_opp_set_voltage().
>
> Perhaps it should be possible to add dev_pm_opp_get_regulator() that

What's wrong with getting the regulator in the driver as well ? Apart from the
OPP core ?

> will return the OPP table regulator in order to allow driver to use the
> regulator directly. But I'm not sure whether this is a much better
> option than the opp_sync_regulators() and opp_set_voltage() APIs.

set_voltage() is still fine as there is some data that the OPP core has, but
sync_regulator() has nothing to do with OPP core.

And this may lead to more wrapper helpers in the OPP core, which I am afraid of.
And so even if it is not the best, I would like the OPP core to provide the data
and not get into this. Ofcourse there is an exception to this, opp_set_rate.

--
viresh

2020-12-23 06:02:50

by Viresh Kumar

[permalink] [raw]
Subject: Re: [PATCH v2 15/48] opp: Support set_opp() customization without requiring to use regulators

On 17-12-20, 21:06, Dmitry Osipenko wrote:
> Support set_opp() customization without requiring to use regulators. This
> is needed by drivers which want to use dev_pm_opp_set_rate() for changing
> rates of a multiple clocks and don't need to touch regulator.
>
> One example is NVIDIA Tegra30/114 SoCs which have two sibling 3D hardware
> units which should be use to the same clock rate, meanwhile voltage
> scaling is done using a power domain. In this case OPP table doesn't have
> a regulator, causing a NULL dereference in _set_opp_custom().
>
> Signed-off-by: Dmitry Osipenko <[email protected]>
> ---
> drivers/opp/core.c | 16 ++++++++++++----
> 1 file changed, 12 insertions(+), 4 deletions(-)
>
> diff --git a/drivers/opp/core.c b/drivers/opp/core.c
> index 3d02fe33630b..625dae7a5ecb 100644
> --- a/drivers/opp/core.c
> +++ b/drivers/opp/core.c
> @@ -828,17 +828,25 @@ static int _set_opp_custom(const struct opp_table *opp_table,
> struct dev_pm_opp_supply *old_supply,
> struct dev_pm_opp_supply *new_supply)
> {
> - struct dev_pm_set_opp_data *data;
> + struct dev_pm_set_opp_data *data, tmp_data;
> + unsigned int regulator_count;
> int size;
>
> - data = opp_table->set_opp_data;
> + if (opp_table->set_opp_data) {
> + data = opp_table->set_opp_data;
> + regulator_count = opp_table->regulator_count;
> + } else {
> + data = &tmp_data;
> + regulator_count = 0;
> + }
> +

We should use the same structure, you can add some checks but not replace the
structure altogether.

> data->regulators = opp_table->regulators;
> - data->regulator_count = opp_table->regulator_count;
> + data->regulator_count = regulator_count;
> data->clk = opp_table->clk;
> data->dev = dev;
>
> data->old_opp.rate = old_freq;
> - size = sizeof(*old_supply) * opp_table->regulator_count;
> + size = sizeof(*old_supply) * regulator_count;
> if (!old_supply)
> memset(data->old_opp.supplies, 0, size);
> else

--
viresh

2020-12-23 20:38:50

by Dmitry Osipenko

[permalink] [raw]
Subject: Re: [PATCH v2 19/48] opp: Fix adding OPP entries in a wrong order if rate is unavailable

23.12.2020 07:34, Viresh Kumar пишет:
> On 22-12-20, 22:19, Dmitry Osipenko wrote:
>> 22.12.2020 12:12, Viresh Kumar пишет:
>>> On 17-12-20, 21:06, Dmitry Osipenko wrote:
>>>> Fix adding OPP entries in a wrong (opposite) order if OPP rate is
>>>> unavailable. The OPP comparison is erroneously skipped if OPP rate is
>>>> missing, thus OPPs are left unsorted.
>>>>
>>>> Signed-off-by: Dmitry Osipenko <[email protected]>
>>>> ---
>>>> drivers/opp/core.c | 23 ++++++++++++-----------
>>>> drivers/opp/opp.h | 2 +-
>>>> 2 files changed, 13 insertions(+), 12 deletions(-)
>>>>
>>>> diff --git a/drivers/opp/core.c b/drivers/opp/core.c
>>>> index 34f7e530d941..5c7f130a8de2 100644
>>>> --- a/drivers/opp/core.c
>>>> +++ b/drivers/opp/core.c
>>>> @@ -1531,9 +1531,10 @@ static bool _opp_supported_by_regulators(struct dev_pm_opp *opp,
>>>> return true;
>>>> }
>>>>
>>>> -int _opp_compare_key(struct dev_pm_opp *opp1, struct dev_pm_opp *opp2)
>>>> +int _opp_compare_key(struct dev_pm_opp *opp1, struct dev_pm_opp *opp2,
>>>> + bool rate_not_available)
>>>> {
>>>> - if (opp1->rate != opp2->rate)
>>>> + if (!rate_not_available && opp1->rate != opp2->rate)
>>>
>>> rate will be 0 for both the OPPs here if rate_not_available is true and so this
>>> change shouldn't be required.
>>
>> The rate_not_available is negated in the condition. This change is
>> required because both rates are 0 and then we should proceed to the
>> levels comparison.
>
> Won't that happen without this patch ?

No

2020-12-23 20:40:00

by Dmitry Osipenko

[permalink] [raw]
Subject: Re: [PATCH v2 11/48] opp: Add dev_pm_opp_find_level_ceil()

23.12.2020 07:19, Viresh Kumar пишет:
> On 22-12-20, 22:15, Dmitry Osipenko wrote:
>> 22.12.2020 09:42, Viresh Kumar пишет:
>>> On 17-12-20, 21:06, Dmitry Osipenko wrote:
>>>> Add a ceil version of the dev_pm_opp_find_level(). It's handy to have if
>>>> levels don't start from 0 in OPP table and zero usually means a minimal
>>>> level.
>>>>
>>>> Signed-off-by: Dmitry Osipenko <[email protected]>
>>>
>>> Why doesn't the exact version work for you here ?
>>>
>>
>> The exact version won't find OPP for level=0 if levels don't start with
>> 0, where 0 means that minimal level is desired.
>
> Right, but why do you need to send 0 for your platform ?
>

To put power domain into the lowest performance state when device is idling.

https://elixir.bootlin.com/linux/v5.10-rc2/source/drivers/opp/core.c#L897

https://elixir.bootlin.com/linux/v5.10-rc2/source/drivers/opp/core.c#L785

Also please see patch 32, tegra_clock_runtime_suspend().

2020-12-23 20:40:19

by Dmitry Osipenko

[permalink] [raw]
Subject: Re: [PATCH v2 28/48] soc/tegra: Introduce core power domain driver

23.12.2020 08:57, Viresh Kumar пишет:
> On 22-12-20, 22:39, Dmitry Osipenko wrote:
>> 22.12.2020 22:21, Dmitry Osipenko пишет:
>>>>> + if (IS_ERR(opp)) {
>>>>> + dev_err(&genpd->dev, "failed to find OPP for level %u: %pe\n",
>>>>> + level, opp);
>>>>> + return PTR_ERR(opp);
>>>>> + }
>>>>> +
>>>>> + err = dev_pm_opp_set_voltage(&genpd->dev, opp);
>>>> IIUC, you implemented this callback because you want to use the voltage triplet
>>>> present in the OPP table ?
>>>>
>>>> And so you are setting the regulator ("power") later in this patch ?
>>> yes
>>>
>>>> I am not in favor of implementing this routine, as it just adds a wrapper above
>>>> the regulator API. What you should be doing rather is get the regulator by
>>>> yourself here (instead of depending on the OPP core). And then you can do
>>>> dev_pm_opp_get_voltage() here and set the voltage yourself. You may want to
>>>> implement a version supporting triplet here though for the same.
>>>>
>>>> And you won't require the sync version of the API as well then.
>>>>
>>> That's what I initially did for this driver. I don't mind to revert back
>>> to the initial variant in v3, it appeared to me that it will be nicer
>>> and cleaner to have OPP API managing everything here.
>>
>> I forgot one important detail (why the initial variant wasn't good)..
>> OPP entries that have unsupportable voltages should be filtered out and
>> OPP core performs the filtering only if regulator is assigned to the OPP
>> table.
>>
>> If regulator is assigned to the OPP table, then we need to use OPP API
>> for driving the regulator, hence that's why I added
>> dev_pm_opp_sync_regulators() and dev_pm_opp_set_voltage().
>>
>> Perhaps it should be possible to add dev_pm_opp_get_regulator() that
>
> What's wrong with getting the regulator in the driver as well ? Apart from the
> OPP core ?

The voltage syncing should be done for each consumer regulator
individually [1].

Secondly, regulator core doesn't work well today if the same regulator
is requested more than one time for the same device.

>> will return the OPP table regulator in order to allow driver to use the
>> regulator directly. But I'm not sure whether this is a much better
>> option than the opp_sync_regulators() and opp_set_voltage() APIs.
>
> set_voltage() is still fine as there is some data that the OPP core has, but
> sync_regulator() has nothing to do with OPP core.
>
> And this may lead to more wrapper helpers in the OPP core, which I am afraid of.
> And so even if it is not the best, I would like the OPP core to provide the data
> and not get into this. Ofcourse there is an exception to this, opp_set_rate.
>

The regulator_sync_voltage() should be invoked only if voltage was
changed previously [1].

The OPP core already has the info about whether voltage was changed and
it provides the necessary locking for both set_voltage() and
sync_regulator(). Perhaps I'll need to duplicate that functionality in
the PD driver, instead of making it all generic and re-usable by other
drivers.

[1]
https://elixir.bootlin.com/linux/v5.10.2/source/drivers/regulator/core.c#L4107

2020-12-23 20:41:02

by Dmitry Osipenko

[permalink] [raw]
Subject: Re: [PATCH v2 15/48] opp: Support set_opp() customization without requiring to use regulators

23.12.2020 09:01, Viresh Kumar пишет:
> On 17-12-20, 21:06, Dmitry Osipenko wrote:
>> Support set_opp() customization without requiring to use regulators. This
>> is needed by drivers which want to use dev_pm_opp_set_rate() for changing
>> rates of a multiple clocks and don't need to touch regulator.
>>
>> One example is NVIDIA Tegra30/114 SoCs which have two sibling 3D hardware
>> units which should be use to the same clock rate, meanwhile voltage
>> scaling is done using a power domain. In this case OPP table doesn't have
>> a regulator, causing a NULL dereference in _set_opp_custom().
>>
>> Signed-off-by: Dmitry Osipenko <[email protected]>
>> ---
>> drivers/opp/core.c | 16 ++++++++++++----
>> 1 file changed, 12 insertions(+), 4 deletions(-)
>>
>> diff --git a/drivers/opp/core.c b/drivers/opp/core.c
>> index 3d02fe33630b..625dae7a5ecb 100644
>> --- a/drivers/opp/core.c
>> +++ b/drivers/opp/core.c
>> @@ -828,17 +828,25 @@ static int _set_opp_custom(const struct opp_table *opp_table,
>> struct dev_pm_opp_supply *old_supply,
>> struct dev_pm_opp_supply *new_supply)
>> {
>> - struct dev_pm_set_opp_data *data;
>> + struct dev_pm_set_opp_data *data, tmp_data;
>> + unsigned int regulator_count;
>> int size;
>>
>> - data = opp_table->set_opp_data;
>> + if (opp_table->set_opp_data) {
>> + data = opp_table->set_opp_data;
>> + regulator_count = opp_table->regulator_count;
>> + } else {
>> + data = &tmp_data;
>> + regulator_count = 0;
>> + }
>> +
>
> We should use the same structure, you can add some checks but not replace the
> structure altogether.

Well, there is no "same structure", the opp_table->set_opp_data is NULL
there.

I can re-write it like this if it looks better to you:

static int _set_opp_custom(...)
{
struct dev_pm_set_opp_data *data;
unsigned int regulator_count;
int size;

+ if (!opp_table->set_opp_data) {
+ struct dev_pm_set_opp_data freq_data = {};
+
+ freq_data.dev = dev;
+ freq_data.clk = opp_table->clk;
+ freq_data.new_opp.rate = freq;
+ freq_data.old_opp.rate = old_freq;
+
+ return opp_table->set_opp(&freq_data);
+ }

2020-12-23 20:51:43

by Dmitry Osipenko

[permalink] [raw]
Subject: Re: [PATCH v2 14/48] opp: Filter out OPPs based on availability of a required-OPP

23.12.2020 07:22, Viresh Kumar пишет:
> On 22-12-20, 22:17, Dmitry Osipenko wrote:
>> 22.12.2020 11:59, Viresh Kumar пишет:
>>> On 17-12-20, 21:06, Dmitry Osipenko wrote:
>>>> A required OPP may not be available, and thus, all OPPs which are using
>>>> this required OPP should be unavailable too.
>>>>
>>>> Signed-off-by: Dmitry Osipenko <[email protected]>
>>>> ---
>>>> drivers/opp/core.c | 11 ++++++++++-
>>>> 1 file changed, 10 insertions(+), 1 deletion(-)
>>>
>>> Please send a separate patchset for fixes, as these can also go to 5.11 itself.
>>
>> Alright, although I don't think that this patch fixes any problems for
>> existing OPP users.
>
> Because nobody is using this feature, but otherwise this is a fix for me.
>
>>>> diff --git a/drivers/opp/core.c b/drivers/opp/core.c
>>>> index d9feb7639598..3d02fe33630b 100644
>>>> --- a/drivers/opp/core.c
>>>> +++ b/drivers/opp/core.c
>>>> @@ -1588,7 +1588,7 @@ int _opp_add(struct device *dev, struct dev_pm_opp *new_opp,
>>>> struct opp_table *opp_table, bool rate_not_available)
>>>> {
>>>> struct list_head *head;
>>>> - int ret;
>>>> + int i, ret;
>>>>
>>>> mutex_lock(&opp_table->lock);
>>>> head = &opp_table->opp_list;
>>>> @@ -1615,6 +1615,15 @@ int _opp_add(struct device *dev, struct dev_pm_opp *new_opp,
>>>> __func__, new_opp->rate);
>>>> }
>>>>
>>>> + for (i = 0; i < opp_table->required_opp_count && new_opp->available; i++) {
>>>> + if (new_opp->required_opps[i]->available)
>>>> + continue;
>>>> +
>>>> + new_opp->available = false;
>>>> + dev_warn(dev, "%s: OPP not supported by required OPP %pOF (%lu)\n",
>>>> + __func__, new_opp->required_opps[i]->np, new_opp->rate);
>>>
>>> Why not just break from here ?
>>
>> The new_opp could be already marked as unavailable by a previous voltage
>> check, hence this loop should be skipped entirely in that case.
>
> Then add a separate check for that before the loop as we don't need that check
> on every iteration here.
>

Perhaps the break will be a better option in this case, since it won't
hurt at all to print the additional message even if OPP was already
disabled by another check. I'll update it in next revision, thanks.

2020-12-23 21:01:42

by Dmitry Osipenko

[permalink] [raw]
Subject: Re: [PATCH v2 28/48] soc/tegra: Introduce core power domain driver

23.12.2020 23:37, Dmitry Osipenko пишет:
> 23.12.2020 08:57, Viresh Kumar пишет:
>> On 22-12-20, 22:39, Dmitry Osipenko wrote:
>>> 22.12.2020 22:21, Dmitry Osipenko пишет:
>>>>>> + if (IS_ERR(opp)) {
>>>>>> + dev_err(&genpd->dev, "failed to find OPP for level %u: %pe\n",
>>>>>> + level, opp);
>>>>>> + return PTR_ERR(opp);
>>>>>> + }
>>>>>> +
>>>>>> + err = dev_pm_opp_set_voltage(&genpd->dev, opp);
>>>>> IIUC, you implemented this callback because you want to use the voltage triplet
>>>>> present in the OPP table ?
>>>>>
>>>>> And so you are setting the regulator ("power") later in this patch ?
>>>> yes
>>>>
>>>>> I am not in favor of implementing this routine, as it just adds a wrapper above
>>>>> the regulator API. What you should be doing rather is get the regulator by
>>>>> yourself here (instead of depending on the OPP core). And then you can do
>>>>> dev_pm_opp_get_voltage() here and set the voltage yourself. You may want to
>>>>> implement a version supporting triplet here though for the same.
>>>>>
>>>>> And you won't require the sync version of the API as well then.
>>>>>
>>>> That's what I initially did for this driver. I don't mind to revert back
>>>> to the initial variant in v3, it appeared to me that it will be nicer
>>>> and cleaner to have OPP API managing everything here.
>>>
>>> I forgot one important detail (why the initial variant wasn't good)..
>>> OPP entries that have unsupportable voltages should be filtered out and
>>> OPP core performs the filtering only if regulator is assigned to the OPP
>>> table.
>>>
>>> If regulator is assigned to the OPP table, then we need to use OPP API
>>> for driving the regulator, hence that's why I added
>>> dev_pm_opp_sync_regulators() and dev_pm_opp_set_voltage().
>>>
>>> Perhaps it should be possible to add dev_pm_opp_get_regulator() that
>>
>> What's wrong with getting the regulator in the driver as well ? Apart from the
>> OPP core ?
>
> The voltage syncing should be done for each consumer regulator
> individually [1].
>
> Secondly, regulator core doesn't work well today if the same regulator
> is requested more than one time for the same device.
>
>>> will return the OPP table regulator in order to allow driver to use the
>>> regulator directly. But I'm not sure whether this is a much better
>>> option than the opp_sync_regulators() and opp_set_voltage() APIs.
>>
>> set_voltage() is still fine as there is some data that the OPP core has, but
>> sync_regulator() has nothing to do with OPP core.
>>
>> And this may lead to more wrapper helpers in the OPP core, which I am afraid of.
>> And so even if it is not the best, I would like the OPP core to provide the data
>> and not get into this. Ofcourse there is an exception to this, opp_set_rate.
>>
>
> The regulator_sync_voltage() should be invoked only if voltage was
> changed previously [1].
>
> The OPP core already has the info about whether voltage was changed and
> it provides the necessary locking for both set_voltage() and
> sync_regulator(). Perhaps I'll need to duplicate that functionality in
> the PD driver, instead of making it all generic and re-usable by other
> drivers.
>
> [1]
> https://elixir.bootlin.com/linux/v5.10.2/source/drivers/regulator/core.c#L4107
>

I just realized that the locking is missing in the v2 patches, something
to fix in the next revision :)

Still I'm in favor of extending the OPP API with the new common
functions. But if you have a strong opinion about that, then I'll try to
work around it in the PD driver in v3.

2020-12-24 04:12:02

by Viresh Kumar

[permalink] [raw]
Subject: Re: [PATCH v2 15/48] opp: Support set_opp() customization without requiring to use regulators

On 23-12-20, 23:38, Dmitry Osipenko wrote:
> Well, there is no "same structure", the opp_table->set_opp_data is NULL
> there.

Right, I saw that yesterday. What I meant was that we need to start allocating
the structure for this case now.

--
viresh

2020-12-24 06:31:12

by Viresh Kumar

[permalink] [raw]
Subject: Re: [PATCH v2 19/48] opp: Fix adding OPP entries in a wrong order if rate is unavailable

On 23-12-20, 23:36, Dmitry Osipenko wrote:
> 23.12.2020 07:34, Viresh Kumar пишет:
> > On 22-12-20, 22:19, Dmitry Osipenko wrote:
> >> 22.12.2020 12:12, Viresh Kumar пишет:
> >>> rate will be 0 for both the OPPs here if rate_not_available is true and so this
> >>> change shouldn't be required.
> >>
> >> The rate_not_available is negated in the condition. This change is
> >> required because both rates are 0 and then we should proceed to the
> >> levels comparison.
> >
> > Won't that happen without this patch ?
>
> No

This is how the code looks like currently:

int _opp_compare_key(struct dev_pm_opp *opp1, struct dev_pm_opp *opp2)
{
if (opp1->rate != opp2->rate)
return opp1->rate < opp2->rate ? -1 : 1;
if (opp1->bandwidth && opp2->bandwidth &&
opp1->bandwidth[0].peak != opp2->bandwidth[0].peak)
return opp1->bandwidth[0].peak < opp2->bandwidth[0].peak ? -1 : 1;
if (opp1->level != opp2->level)
return opp1->level < opp2->level ? -1 : 1;
return 0;
}

Lets consider the case you are focussing on, where rate is 0 for both the OPPs,
bandwidth isn't there and we want to run the level comparison here.

Since both the rates are 0, (opp1->rate != opp2->rate) will fail and so we will
move to bandwidth check which will fail too. And so we will get to the level
comparison.

What am I missing here ? I am sure there is something for sure as you won't have
missed this..

--
viresh

2020-12-24 06:47:15

by Viresh Kumar

[permalink] [raw]
Subject: Re: [PATCH v2 11/48] opp: Add dev_pm_opp_find_level_ceil()

On 23-12-20, 23:37, Dmitry Osipenko wrote:
> 23.12.2020 07:19, Viresh Kumar пишет:
> > On 22-12-20, 22:15, Dmitry Osipenko wrote:
> >> 22.12.2020 09:42, Viresh Kumar пишет:
> >>> On 17-12-20, 21:06, Dmitry Osipenko wrote:
> >>>> Add a ceil version of the dev_pm_opp_find_level(). It's handy to have if
> >>>> levels don't start from 0 in OPP table and zero usually means a minimal
> >>>> level.
> >>>>
> >>>> Signed-off-by: Dmitry Osipenko <[email protected]>
> >>>
> >>> Why doesn't the exact version work for you here ?
> >>>
> >>
> >> The exact version won't find OPP for level=0 if levels don't start with
> >> 0, where 0 means that minimal level is desired.
> >
> > Right, but why do you need to send 0 for your platform ?
> >
>
> To put power domain into the lowest performance state when device is idling.

I see. So you really want to set it to the lowest state or just take the vote
out ? Which may end up powering off the domain in the worst case ?

--
viresh

2020-12-24 06:53:16

by Viresh Kumar

[permalink] [raw]
Subject: Re: [PATCH v2 28/48] soc/tegra: Introduce core power domain driver

On 23-12-20, 23:37, Dmitry Osipenko wrote:
> 23.12.2020 08:57, Viresh Kumar пишет:
> > What's wrong with getting the regulator in the driver as well ? Apart from the
> > OPP core ?
>
> The voltage syncing should be done for each consumer regulator
> individually [1].
>
> Secondly, regulator core doesn't work well today if the same regulator
> is requested more than one time for the same device.

Hmm...

> >> will return the OPP table regulator in order to allow driver to use the
> >> regulator directly. But I'm not sure whether this is a much better
> >> option than the opp_sync_regulators() and opp_set_voltage() APIs.
> >
> > set_voltage() is still fine as there is some data that the OPP core has, but
> > sync_regulator() has nothing to do with OPP core.
> >
> > And this may lead to more wrapper helpers in the OPP core, which I am afraid of.
> > And so even if it is not the best, I would like the OPP core to provide the data
> > and not get into this. Ofcourse there is an exception to this, opp_set_rate.
> >
>
> The regulator_sync_voltage() should be invoked only if voltage was
> changed previously [1].
>
> The OPP core already has the info about whether voltage was changed and
> it provides the necessary locking for both set_voltage() and
> sync_regulator(). Perhaps I'll need to duplicate that functionality in
> the PD driver, instead of making it all generic and re-usable by other
> drivers.
>
> [1]
> https://elixir.bootlin.com/linux/v5.10.2/source/drivers/regulator/core.c#L4107

Lets do it in the OPP core and see where we go.

--
viresh

2020-12-24 12:16:22

by Dmitry Osipenko

[permalink] [raw]
Subject: Re: [PATCH v2 19/48] opp: Fix adding OPP entries in a wrong order if rate is unavailable

24.12.2020 09:28, Viresh Kumar пишет:
> On 23-12-20, 23:36, Dmitry Osipenko wrote:
>> 23.12.2020 07:34, Viresh Kumar пишет:
>>> On 22-12-20, 22:19, Dmitry Osipenko wrote:
>>>> 22.12.2020 12:12, Viresh Kumar пишет:
>>>>> rate will be 0 for both the OPPs here if rate_not_available is true and so this
>>>>> change shouldn't be required.
>>>>
>>>> The rate_not_available is negated in the condition. This change is
>>>> required because both rates are 0 and then we should proceed to the
>>>> levels comparison.
>>>
>>> Won't that happen without this patch ?
>>
>> No
>
> This is how the code looks like currently:
>
> int _opp_compare_key(struct dev_pm_opp *opp1, struct dev_pm_opp *opp2)
> {
> if (opp1->rate != opp2->rate)
> return opp1->rate < opp2->rate ? -1 : 1;
> if (opp1->bandwidth && opp2->bandwidth &&
> opp1->bandwidth[0].peak != opp2->bandwidth[0].peak)
> return opp1->bandwidth[0].peak < opp2->bandwidth[0].peak ? -1 : 1;
> if (opp1->level != opp2->level)
> return opp1->level < opp2->level ? -1 : 1;
> return 0;
> }
>
> Lets consider the case you are focussing on, where rate is 0 for both the OPPs,
> bandwidth isn't there and we want to run the level comparison here.
>
> Since both the rates are 0, (opp1->rate != opp2->rate) will fail and so we will
> move to bandwidth check which will fail too. And so we will get to the level
> comparison.
>
> What am I missing here ? I am sure there is something for sure as you won't have
> missed this..
>

Ah, you're right. It was me who was missing something as I see now,
after taking a closer look and trying to implement yours suggestion, my
bad. I'll improve this patch in the next revision, thanks!

2020-12-24 12:16:41

by Dmitry Osipenko

[permalink] [raw]
Subject: Re: [PATCH v2 28/48] soc/tegra: Introduce core power domain driver

24.12.2020 09:51, Viresh Kumar пишет:
> On 23-12-20, 23:37, Dmitry Osipenko wrote:
>> 23.12.2020 08:57, Viresh Kumar пишет:
>>> What's wrong with getting the regulator in the driver as well ? Apart from the
>>> OPP core ?
>>
>> The voltage syncing should be done for each consumer regulator
>> individually [1].
>>
>> Secondly, regulator core doesn't work well today if the same regulator
>> is requested more than one time for the same device.
>
> Hmm...
>
>>>> will return the OPP table regulator in order to allow driver to use the
>>>> regulator directly. But I'm not sure whether this is a much better
>>>> option than the opp_sync_regulators() and opp_set_voltage() APIs.
>>>
>>> set_voltage() is still fine as there is some data that the OPP core has, but
>>> sync_regulator() has nothing to do with OPP core.
>>>
>>> And this may lead to more wrapper helpers in the OPP core, which I am afraid of.
>>> And so even if it is not the best, I would like the OPP core to provide the data
>>> and not get into this. Ofcourse there is an exception to this, opp_set_rate.
>>>
>>
>> The regulator_sync_voltage() should be invoked only if voltage was
>> changed previously [1].
>>
>> The OPP core already has the info about whether voltage was changed and
>> it provides the necessary locking for both set_voltage() and
>> sync_regulator(). Perhaps I'll need to duplicate that functionality in
>> the PD driver, instead of making it all generic and re-usable by other
>> drivers.
>>
>> [1]
>> https://elixir.bootlin.com/linux/v5.10.2/source/drivers/regulator/core.c#L4107
>
> Lets do it in the OPP core and see where we go.
>

Alright, thank you.

2020-12-24 12:19:47

by Dmitry Osipenko

[permalink] [raw]
Subject: Re: [PATCH v2 15/48] opp: Support set_opp() customization without requiring to use regulators

24.12.2020 07:10, Viresh Kumar пишет:
> On 23-12-20, 23:38, Dmitry Osipenko wrote:
>> Well, there is no "same structure", the opp_table->set_opp_data is NULL
>> there.
>
> Right, I saw that yesterday. What I meant was that we need to start allocating
> the structure for this case now.
>

Okay, that will be a bit bigger change than this v2. I'll try it
implement yours suggestion in the next revision, thanks.

2020-12-24 13:02:47

by Dmitry Osipenko

[permalink] [raw]
Subject: Re: [PATCH v2 11/48] opp: Add dev_pm_opp_find_level_ceil()

24.12.2020 09:43, Viresh Kumar пишет:
> On 23-12-20, 23:37, Dmitry Osipenko wrote:
>> 23.12.2020 07:19, Viresh Kumar пишет:
>>> On 22-12-20, 22:15, Dmitry Osipenko wrote:
>>>> 22.12.2020 09:42, Viresh Kumar пишет:
>>>>> On 17-12-20, 21:06, Dmitry Osipenko wrote:
>>>>>> Add a ceil version of the dev_pm_opp_find_level(). It's handy to have if
>>>>>> levels don't start from 0 in OPP table and zero usually means a minimal
>>>>>> level.
>>>>>>
>>>>>> Signed-off-by: Dmitry Osipenko <[email protected]>
>>>>>
>>>>> Why doesn't the exact version work for you here ?
>>>>>
>>>>
>>>> The exact version won't find OPP for level=0 if levels don't start with
>>>> 0, where 0 means that minimal level is desired.
>>>
>>> Right, but why do you need to send 0 for your platform ?
>>>
>>
>> To put power domain into the lowest performance state when device is idling.
>
> I see. So you really want to set it to the lowest state or just take the vote
> out ? Which may end up powering off the domain in the worst case ?
>

In a device driver I want to set PD to the lowest performance state by
removing the performance vote when dev_pm_opp_set_rate(dev, 0) is
invoked by the driver.

The OPP core already does this, but if OPP levels don't start from 0 in
a device-tree for PD, then it currently doesn't work since there is a
need to get a rounded-up performance state because
dev_pm_opp_set_voltage() takes OPP entry for the argument (patches 9 and
28).

The PD powering off and performance-changes are separate from each other
in the GENPD core. The GENPD core automatically turns off domain when
all devices within the domain are suspended by system-suspend or RPM.

The performance state of a power domain is controlled solely by a device
driver. GENPD core only aggregates the performance requests, it doesn't
change the performance state of a domain by itself when device is
suspended or resumed, IIUC this is intentional. And I want to put domain
into lowest performance state when device is suspended.

2020-12-28 06:24:48

by Viresh Kumar

[permalink] [raw]
Subject: Re: [PATCH v2 11/48] opp: Add dev_pm_opp_find_level_ceil()

On 24-12-20, 16:00, Dmitry Osipenko wrote:
> In a device driver I want to set PD to the lowest performance state by
> removing the performance vote when dev_pm_opp_set_rate(dev, 0) is
> invoked by the driver.
>
> The OPP core already does this, but if OPP levels don't start from 0 in
> a device-tree for PD, then it currently doesn't work since there is a
> need to get a rounded-up performance state because
> dev_pm_opp_set_voltage() takes OPP entry for the argument (patches 9 and
> 28).
>
> The PD powering off and performance-changes are separate from each other
> in the GENPD core. The GENPD core automatically turns off domain when
> all devices within the domain are suspended by system-suspend or RPM.
>
> The performance state of a power domain is controlled solely by a device
> driver. GENPD core only aggregates the performance requests, it doesn't
> change the performance state of a domain by itself when device is
> suspended or resumed, IIUC this is intentional. And I want to put domain
> into lowest performance state when device is suspended.

Right, so if you really want to just drop the performance vote, then with a
value of 0 for the performance state the call will reach to your genpd's
callback ->set_performance_state(). Just as dev_pm_opp_set_rate() accepts the
frequency to be 0, I would expect dev_pm_opp_set_rate() to accept opp argument
as NULL and in that case set voltage to 0 and do regulator_disable() as well.
Won't that work better than going for the lowest voltage ?

--
viresh

2020-12-28 15:20:16

by Dmitry Osipenko

[permalink] [raw]
Subject: Re: [PATCH v2 11/48] opp: Add dev_pm_opp_find_level_ceil()

28.12.2020 09:22, Viresh Kumar пишет:
> On 24-12-20, 16:00, Dmitry Osipenko wrote:
>> In a device driver I want to set PD to the lowest performance state by
>> removing the performance vote when dev_pm_opp_set_rate(dev, 0) is
>> invoked by the driver.
>>
>> The OPP core already does this, but if OPP levels don't start from 0 in
>> a device-tree for PD, then it currently doesn't work since there is a
>> need to get a rounded-up performance state because
>> dev_pm_opp_set_voltage() takes OPP entry for the argument (patches 9 and
>> 28).
>>
>> The PD powering off and performance-changes are separate from each other
>> in the GENPD core. The GENPD core automatically turns off domain when
>> all devices within the domain are suspended by system-suspend or RPM.
>>
>> The performance state of a power domain is controlled solely by a device
>> driver. GENPD core only aggregates the performance requests, it doesn't
>> change the performance state of a domain by itself when device is
>> suspended or resumed, IIUC this is intentional. And I want to put domain
>> into lowest performance state when device is suspended.
>
> Right, so if you really want to just drop the performance vote, then with a
> value of 0 for the performance state the call will reach to your genpd's
> callback ->set_performance_state(). Just as dev_pm_opp_set_rate() accepts the
> frequency to be 0, I would expect dev_pm_opp_set_rate() to accept opp argument
> as NULL and in that case set voltage to 0 and do regulator_disable() as well.
> Won't that work better than going for the lowest voltage ?
>

We can make dev_pm_opp_set_voltage() to accept OPP=NULL in order to
disable the regulator, like it's done for dev_pm_opp_set_rate(dev, 0).
Although, I don't need this kind of behaviour for the Tegra PD driver,
and thus, would prefer to leave this for somebody else to implement in
the future, once it will be really needed.

Still we need the dev_pm_opp_find_level_ceil() because level=0 means
that we want to set PD to the lowest (minimal) performance state, i.e.
it doesn't necessarily mean that we want to set the voltage to 0 and
disable the PD entirely. GENPD has a separate controls for on/off.

2020-12-30 04:48:40

by Viresh Kumar

[permalink] [raw]
Subject: Re: [PATCH v2 11/48] opp: Add dev_pm_opp_find_level_ceil()

On 28-12-20, 17:03, Dmitry Osipenko wrote:
> 28.12.2020 09:22, Viresh Kumar пишет:
> > On 24-12-20, 16:00, Dmitry Osipenko wrote:
> >> In a device driver I want to set PD to the lowest performance state by
> >> removing the performance vote when dev_pm_opp_set_rate(dev, 0) is
> >> invoked by the driver.
> >>
> >> The OPP core already does this, but if OPP levels don't start from 0 in
> >> a device-tree for PD, then it currently doesn't work since there is a
> >> need to get a rounded-up performance state because
> >> dev_pm_opp_set_voltage() takes OPP entry for the argument (patches 9 and
> >> 28).
> >>
> >> The PD powering off and performance-changes are separate from each other
> >> in the GENPD core. The GENPD core automatically turns off domain when
> >> all devices within the domain are suspended by system-suspend or RPM.
> >>
> >> The performance state of a power domain is controlled solely by a device
> >> driver. GENPD core only aggregates the performance requests, it doesn't
> >> change the performance state of a domain by itself when device is
> >> suspended or resumed, IIUC this is intentional. And I want to put domain
> >> into lowest performance state when device is suspended.
> >
> > Right, so if you really want to just drop the performance vote, then with a
> > value of 0 for the performance state the call will reach to your genpd's
> > callback ->set_performance_state(). Just as dev_pm_opp_set_rate() accepts the
> > frequency to be 0, I would expect dev_pm_opp_set_rate() to accept opp argument
> > as NULL and in that case set voltage to 0 and do regulator_disable() as well.
> > Won't that work better than going for the lowest voltage ?
> >
>
> We can make dev_pm_opp_set_voltage() to accept OPP=NULL in order to
> disable the regulator, like it's done for dev_pm_opp_set_rate(dev, 0).
> Although, I don't need this kind of behaviour for the Tegra PD driver,
> and thus, would prefer to leave this for somebody else to implement in
> the future, once it will be really needed.
>
> Still we need the dev_pm_opp_find_level_ceil() because level=0 means
> that we want to set PD to the lowest (minimal) performance state, i.e.
> it doesn't necessarily mean that we want to set the voltage to 0 and
> disable the PD entirely. GENPD has a separate controls for on/off.

Ok.

--
viresh

2020-12-30 14:03:47

by Dmitry Osipenko

[permalink] [raw]
Subject: Re: [PATCH v2 11/48] opp: Add dev_pm_opp_find_level_ceil()

30.12.2020 07:46, Viresh Kumar пишет:
> On 28-12-20, 17:03, Dmitry Osipenko wrote:
>> 28.12.2020 09:22, Viresh Kumar пишет:
>>> On 24-12-20, 16:00, Dmitry Osipenko wrote:
>>>> In a device driver I want to set PD to the lowest performance state by
>>>> removing the performance vote when dev_pm_opp_set_rate(dev, 0) is
>>>> invoked by the driver.
>>>>
>>>> The OPP core already does this, but if OPP levels don't start from 0 in
>>>> a device-tree for PD, then it currently doesn't work since there is a
>>>> need to get a rounded-up performance state because
>>>> dev_pm_opp_set_voltage() takes OPP entry for the argument (patches 9 and
>>>> 28).
>>>>
>>>> The PD powering off and performance-changes are separate from each other
>>>> in the GENPD core. The GENPD core automatically turns off domain when
>>>> all devices within the domain are suspended by system-suspend or RPM.
>>>>
>>>> The performance state of a power domain is controlled solely by a device
>>>> driver. GENPD core only aggregates the performance requests, it doesn't
>>>> change the performance state of a domain by itself when device is
>>>> suspended or resumed, IIUC this is intentional. And I want to put domain
>>>> into lowest performance state when device is suspended.
>>>
>>> Right, so if you really want to just drop the performance vote, then with a
>>> value of 0 for the performance state the call will reach to your genpd's
>>> callback ->set_performance_state(). Just as dev_pm_opp_set_rate() accepts the
>>> frequency to be 0, I would expect dev_pm_opp_set_rate() to accept opp argument
>>> as NULL and in that case set voltage to 0 and do regulator_disable() as well.
>>> Won't that work better than going for the lowest voltage ?
>>>
>>
>> We can make dev_pm_opp_set_voltage() to accept OPP=NULL in order to
>> disable the regulator, like it's done for dev_pm_opp_set_rate(dev, 0).
>> Although, I don't need this kind of behaviour for the Tegra PD driver,
>> and thus, would prefer to leave this for somebody else to implement in
>> the future, once it will be really needed.
>>
>> Still we need the dev_pm_opp_find_level_ceil() because level=0 means
>> that we want to set PD to the lowest (minimal) performance state, i.e.
>> it doesn't necessarily mean that we want to set the voltage to 0 and
>> disable the PD entirely. GENPD has a separate controls for on/off.
>
> Ok.
>

I'll separate the OPP patches from this series and will prepare v3,
thank you for the review!

2020-12-30 14:58:08

by Dmitry Osipenko

[permalink] [raw]
Subject: Re: [PATCH v2 23/48] soc/tegra: pmc: Pulse resets after removing power clamp

17.12.2020 21:06, Dmitry Osipenko пишет:
> The GR3D1 hardware unit needs to pulse hardware reset after removing power
> clamp, otherwise reset won't be deasserted. Hence give reset a pulse after
> removing the clamp. This stayed unnoticed previously because power
> management wasn't supported by the 3D driver until recently and all power
> gates are usually ungated after bootloader by default.
>
> Signed-off-by: Dmitry Osipenko <[email protected]>
> ---
> drivers/soc/tegra/pmc.c | 8 ++++++++
> 1 file changed, 8 insertions(+)
>
> diff --git a/drivers/soc/tegra/pmc.c b/drivers/soc/tegra/pmc.c
> index fd2ba3c59178..985373ce52b1 100644
> --- a/drivers/soc/tegra/pmc.c
> +++ b/drivers/soc/tegra/pmc.c
> @@ -654,6 +654,14 @@ static int tegra_powergate_power_up(struct tegra_powergate *pg,
>
> usleep_range(10, 20);
>
> + /*
> + * Some hardware blocks may need a 0->1->0 reset pulse in order
> + * to propagate the reset, Tegra30 3D1 is one example.
> + */
> + err = reset_control_reset(pg->reset);
> + if (err)
> + goto powergate_off;
> +
> if (pg->pmc->soc->needs_mbist_war)
> err = tegra210_clk_handle_mbist_war(pg->id);
> if (err)
>

After some more testing, I found that 3D1 is reset properly if its
memory client is placed in reset before power domain is turned off. The
memory client resetting is completely missed in v2. I'll improve it in
v3 and then this PMC workaround-patch won't be needed anymore.

2021-01-05 17:14:17

by Krzysztof Kozlowski

[permalink] [raw]
Subject: Re: [PATCH v2 00/48] Introduce core voltage scaling for NVIDIA Tegra20/30 SoCs

On Thu, Dec 17, 2020 at 09:05:50PM +0300, Dmitry Osipenko wrote:
> Introduce core voltage scaling for NVIDIA Tegra20/30 SoCs, which reduces
> power consumption and heating of the Tegra chips. Tegra SoC has multiple
> hardware units which belong to a core power domain of the SoC and share
> the core voltage. The voltage must be selected in accordance to a minimum
> requirement of every core hardware unit.
>
> The minimum core voltage requirement depends on:
>
> 1. Clock enable state of a hardware unit.
> 2. Clock frequency.
> 3. Unit's internal idling/active state.
>
> This series is tested on Acer A500 (T20), AC100 (T20), Nexus 7 (T30),
> Ouya (T30), TK1 (T124) and some others. I also added voltage scaling to
> the Ventana (T20) and Cardhu (T30) boards which are tested by NVIDIA's CI
> farm. Tegra30 is now couple degrees cooler on Nexus 7 and stays cool on
> Ouya (instead of becoming burning hot) while system is idling. It should
> be possible to improve this further by implementing a more advanced power
> management features for the kernel drivers.
>
> The DVFS support is opt-in for all boards, meaning that older DTBs will
> continue to work like they did it before this series. It should be possible
> to easily add the core voltage scaling support for Tegra114+ SoCs based on
> this grounding work later on, if anyone will want to implement it.

The same comment as for your interconnect work: for sets touching
multiple systems please mention the dependencies between patches in the
cover letter. Not as a reply to such remark like I make here, but as a
separate entry in the cover letter.

Best regards,
Krzysztof

2021-01-07 19:43:17

by Dmitry Osipenko

[permalink] [raw]
Subject: Re: [PATCH v2 00/48] Introduce core voltage scaling for NVIDIA Tegra20/30 SoCs

05.01.2021 20:11, Krzysztof Kozlowski пишет:
> On Thu, Dec 17, 2020 at 09:05:50PM +0300, Dmitry Osipenko wrote:
>> Introduce core voltage scaling for NVIDIA Tegra20/30 SoCs, which reduces
>> power consumption and heating of the Tegra chips. Tegra SoC has multiple
>> hardware units which belong to a core power domain of the SoC and share
>> the core voltage. The voltage must be selected in accordance to a minimum
>> requirement of every core hardware unit.
>>
>> The minimum core voltage requirement depends on:
>>
>> 1. Clock enable state of a hardware unit.
>> 2. Clock frequency.
>> 3. Unit's internal idling/active state.
>>
>> This series is tested on Acer A500 (T20), AC100 (T20), Nexus 7 (T30),
>> Ouya (T30), TK1 (T124) and some others. I also added voltage scaling to
>> the Ventana (T20) and Cardhu (T30) boards which are tested by NVIDIA's CI
>> farm. Tegra30 is now couple degrees cooler on Nexus 7 and stays cool on
>> Ouya (instead of becoming burning hot) while system is idling. It should
>> be possible to improve this further by implementing a more advanced power
>> management features for the kernel drivers.
>>
>> The DVFS support is opt-in for all boards, meaning that older DTBs will
>> continue to work like they did it before this series. It should be possible
>> to easily add the core voltage scaling support for Tegra114+ SoCs based on
>> this grounding work later on, if anyone will want to implement it.
>
> The same comment as for your interconnect work: for sets touching
> multiple systems please mention the dependencies between patches in the
> cover letter. Not as a reply to such remark like I make here, but as a
> separate entry in the cover letter.

I'll describe all the dependencies in the next revision, thanks.

2021-01-11 09:15:04

by Ulf Hansson

[permalink] [raw]
Subject: Re: [PATCH v2 20/48] PM: domains: Make set_performance_state() callback optional

On Thu, 17 Dec 2020 at 19:07, Dmitry Osipenko <[email protected]> wrote:
>
> Make set_performance_state() callback optional in order to remove the
> need from power domain drivers to implement a dummy callback. If callback
> isn't implemented by a GENPD driver, then the performance state is passed
> to the parent domain.
>
> Signed-off-by: Dmitry Osipenko <[email protected]>

Reviewed-by: Ulf Hansson <[email protected]>

This should be ready to be picked up, but maybe it's easier if you
could send this to the linux-pm mailing list separately. Then Rafael
can pick it up.

Kind regards
Uffe

> ---
> drivers/base/power/domain.c | 11 +++++------
> 1 file changed, 5 insertions(+), 6 deletions(-)
>
> diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c
> index 9a14eedacb92..a3e1bfc233d4 100644
> --- a/drivers/base/power/domain.c
> +++ b/drivers/base/power/domain.c
> @@ -339,9 +339,11 @@ static int _genpd_set_performance_state(struct generic_pm_domain *genpd,
> goto err;
> }
>
> - ret = genpd->set_performance_state(genpd, state);
> - if (ret)
> - goto err;
> + if (genpd->set_performance_state) {
> + ret = genpd->set_performance_state(genpd, state);
> + if (ret)
> + goto err;
> + }
>
> genpd->performance_state = state;
> return 0;
> @@ -399,9 +401,6 @@ int dev_pm_genpd_set_performance_state(struct device *dev, unsigned int state)
> if (!genpd)
> return -ENODEV;
>
> - if (unlikely(!genpd->set_performance_state))
> - return -EINVAL;
> -
> if (WARN_ON(!dev->power.subsys_data ||
> !dev->power.subsys_data->domain_data))
> return -EINVAL;
> --
> 2.29.2
>

2021-01-11 09:16:38

by Ulf Hansson

[permalink] [raw]
Subject: Re: [PATCH v2 21/48] PM: domains: Add "performance" column to debug summary

On Thu, 17 Dec 2020 at 19:07, Dmitry Osipenko <[email protected]> wrote:
>
> Add "performance" column to debug summary which shows performance state
> of all power domains and theirs devices.
>
> Signed-off-by: Dmitry Osipenko <[email protected]>

Reviewed-by: Ulf Hansson <[email protected]>

As I stated for patch 20, I suggest you resend this to the linux-pm
mailing list so it can be picked up by Rafael.

Kind regards
Uffe

> ---
> drivers/base/power/domain.c | 22 +++++++++++++++++-----
> 1 file changed, 17 insertions(+), 5 deletions(-)
>
> diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c
> index a3e1bfc233d4..1c60dae8540c 100644
> --- a/drivers/base/power/domain.c
> +++ b/drivers/base/power/domain.c
> @@ -2951,7 +2951,15 @@ static void rtpm_status_str(struct seq_file *s, struct device *dev)
> else
> WARN_ON(1);
>
> - seq_puts(s, p);
> + seq_printf(s, "%-25s ", p);
> +}
> +
> +static void perf_status_str(struct seq_file *s, struct device *dev)
> +{
> + struct generic_pm_domain_data *gpd_data;
> +
> + gpd_data = to_gpd_data(dev->power.subsys_data->domain_data);
> + seq_put_decimal_ull(s, "", gpd_data->performance_state);
> }
>
> static int genpd_summary_one(struct seq_file *s,
> @@ -2979,7 +2987,7 @@ static int genpd_summary_one(struct seq_file *s,
> else
> snprintf(state, sizeof(state), "%s",
> status_lookup[genpd->status]);
> - seq_printf(s, "%-30s %-15s ", genpd->name, state);
> + seq_printf(s, "%-30s %-50s %u", genpd->name, state, genpd->performance_state);
>
> /*
> * Modifications on the list require holding locks on both
> @@ -2987,7 +2995,10 @@ static int genpd_summary_one(struct seq_file *s,
> * Also genpd->name is immutable.
> */
> list_for_each_entry(link, &genpd->parent_links, parent_node) {
> - seq_printf(s, "%s", link->child->name);
> + if (list_is_first(&link->parent_node, &genpd->parent_links))
> + seq_printf(s, "\n%50s ", link->child->name);
> + else
> + seq_printf(s, "%s", link->child->name);
> if (!list_is_last(&link->parent_node, &genpd->parent_links))
> seq_puts(s, ", ");
> }
> @@ -3001,6 +3012,7 @@ static int genpd_summary_one(struct seq_file *s,
>
> seq_printf(s, "\n %-50s ", kobj_path);
> rtpm_status_str(s, pm_data->dev);
> + perf_status_str(s, pm_data->dev);
> kfree(kobj_path);
> }
>
> @@ -3016,9 +3028,9 @@ static int summary_show(struct seq_file *s, void *data)
> struct generic_pm_domain *genpd;
> int ret = 0;
>
> - seq_puts(s, "domain status children\n");
> + seq_puts(s, "domain status children performance\n");
> seq_puts(s, " /device runtime status\n");
> - seq_puts(s, "----------------------------------------------------------------------\n");
> + seq_puts(s, "----------------------------------------------------------------------------------------------\n");
>
> ret = mutex_lock_interruptible(&gpd_list_lock);
> if (ret)
> --
> 2.29.2
>

2021-01-11 11:30:30

by Dmitry Osipenko

[permalink] [raw]
Subject: Re: [PATCH v2 21/48] PM: domains: Add "performance" column to debug summary

11.01.2021 12:13, Ulf Hansson пишет:
> On Thu, 17 Dec 2020 at 19:07, Dmitry Osipenko <[email protected]> wrote:
>>
>> Add "performance" column to debug summary which shows performance state
>> of all power domains and theirs devices.
>>
>> Signed-off-by: Dmitry Osipenko <[email protected]>
>
> Reviewed-by: Ulf Hansson <[email protected]>
>
> As I stated for patch 20, I suggest you resend this to the linux-pm
> mailing list so it can be picked up by Rafael.

Thanks, I'm finalizing v3 with various fixes and improvements over all
the patches, including this one, which will get a slightly improved
seq_printf formatting.

2021-01-12 13:35:30

by Ulf Hansson

[permalink] [raw]
Subject: Re: [PATCH v2 29/48] soc/tegra: pmc: Link domains to the parent Core domain

- trimmed cc-list

On Thu, 17 Dec 2020 at 19:07, Dmitry Osipenko <[email protected]> wrote:
>
> The Core domain is a parent of PMC power domains, hence PMC domains
> should be set up as a sub-domains of the parent (Core) domain if
> "power-domains" phandle presents in a device-tree node of PMC domain.
>
> This allows to propagate GENPD performance changes to the parent Core
> domain if performance change is applied to PMC domain.
>
> Signed-off-by: Dmitry Osipenko <[email protected]>
> ---
> drivers/soc/tegra/pmc.c | 19 +++++++++++++++++++
> 1 file changed, 19 insertions(+)
>
> diff --git a/drivers/soc/tegra/pmc.c b/drivers/soc/tegra/pmc.c
> index 4f96dc7745c4..1a659d1c06d7 100644
> --- a/drivers/soc/tegra/pmc.c
> +++ b/drivers/soc/tegra/pmc.c
> @@ -1236,6 +1236,7 @@ static int tegra_powergate_add(struct tegra_pmc *pmc, struct device_node *np)
> static int tegra_powergate_init(struct tegra_pmc *pmc,
> struct device_node *parent)
> {
> + struct of_phandle_args child_args, parent_args;
> struct device_node *np, *child;
> int err = 0;
>
> @@ -1249,6 +1250,24 @@ static int tegra_powergate_init(struct tegra_pmc *pmc,
> of_node_put(child);
> break;
> }
> +
> + if (of_parse_phandle_with_args(child, "power-domains",
> + "#power-domain-cells",
> + 0, &parent_args))
> + continue;
> +
> + child_args.np = child;
> + child_args.args_count = 0;
> +
> + err = of_genpd_add_subdomain(&parent_args, &child_args);
> + of_node_put(parent_args.np);
> + if (err) {
> + if (err == -ENOENT)
> + err = -EPROBE_DEFER;

Okay. So this special error treatment is needed because
of_genpd_add_subdomain may return -ENOENT, in case the providers for
the parent-domain and child-domain haven't been registered yet.

I suggest we move this into of_genpd_add_subdomain() instead, thus
letting it return -EPROBE_DEFER when there are parent/child nodes
specified in DT, but the providers are lacking.

> +
> + of_node_put(child);
> + break;
> + }
> }
>
> of_node_put(np);
> --
> 2.29.2
>

Kind regards
Uffe

2021-01-12 14:00:58

by Ulf Hansson

[permalink] [raw]
Subject: Re: [PATCH v2 28/48] soc/tegra: Introduce core power domain driver

On Thu, 17 Dec 2020 at 19:07, Dmitry Osipenko <[email protected]> wrote:
>
> NVIDIA Tegra SoCs have multiple power domains, each domain corresponds
> to an external SoC power rail. Core power domain covers vast majority of
> hardware blocks within a Tegra SoC. The voltage of a power domain should
> be set to a value which satisfies all devices within a power domain. Add
> driver for the core power domain in order to manage the voltage state of
> the domain. This allows us to support a system-wide DVFS on Tegra.
>
> Signed-off-by: Dmitry Osipenko <[email protected]>

FYI: from a genpd provider driver point of view, this looks good to
me. However, withholding my ack for the next version, just to make
sure I take a second look.

Kind regards
Uffe

> ---
> drivers/soc/tegra/Kconfig | 6 ++
> drivers/soc/tegra/Makefile | 1 +
> drivers/soc/tegra/core-power-domain.c | 125 ++++++++++++++++++++++++++
> include/soc/tegra/common.h | 6 ++
> 4 files changed, 138 insertions(+)
> create mode 100644 drivers/soc/tegra/core-power-domain.c
>
> diff --git a/drivers/soc/tegra/Kconfig b/drivers/soc/tegra/Kconfig
> index bcd61ae59ba3..fccbc168dd87 100644
> --- a/drivers/soc/tegra/Kconfig
> +++ b/drivers/soc/tegra/Kconfig
> @@ -16,6 +16,7 @@ config ARCH_TEGRA_2x_SOC
> select SOC_TEGRA_COMMON
> select SOC_TEGRA_FLOWCTRL
> select SOC_TEGRA_PMC
> + select SOC_TEGRA_CORE_POWER_DOMAIN
> select SOC_TEGRA20_VOLTAGE_COUPLER
> select TEGRA_TIMER
> help
> @@ -31,6 +32,7 @@ config ARCH_TEGRA_3x_SOC
> select SOC_TEGRA_COMMON
> select SOC_TEGRA_FLOWCTRL
> select SOC_TEGRA_PMC
> + select SOC_TEGRA_CORE_POWER_DOMAIN
> select SOC_TEGRA30_VOLTAGE_COUPLER
> select TEGRA_TIMER
> help
> @@ -170,3 +172,7 @@ config SOC_TEGRA20_VOLTAGE_COUPLER
> config SOC_TEGRA30_VOLTAGE_COUPLER
> bool "Voltage scaling support for Tegra30 SoCs"
> depends on ARCH_TEGRA_3x_SOC || COMPILE_TEST
> +
> +config SOC_TEGRA_CORE_POWER_DOMAIN
> + bool
> + select PM_GENERIC_DOMAINS
> diff --git a/drivers/soc/tegra/Makefile b/drivers/soc/tegra/Makefile
> index 9c809c1814bd..8f1294f954b4 100644
> --- a/drivers/soc/tegra/Makefile
> +++ b/drivers/soc/tegra/Makefile
> @@ -7,3 +7,4 @@ obj-$(CONFIG_SOC_TEGRA_PMC) += pmc.o
> obj-$(CONFIG_SOC_TEGRA_POWERGATE_BPMP) += powergate-bpmp.o
> obj-$(CONFIG_SOC_TEGRA20_VOLTAGE_COUPLER) += regulators-tegra20.o
> obj-$(CONFIG_SOC_TEGRA30_VOLTAGE_COUPLER) += regulators-tegra30.o
> +obj-$(CONFIG_SOC_TEGRA_CORE_POWER_DOMAIN) += core-power-domain.o
> diff --git a/drivers/soc/tegra/core-power-domain.c b/drivers/soc/tegra/core-power-domain.c
> new file mode 100644
> index 000000000000..7c0cec8c79fd
> --- /dev/null
> +++ b/drivers/soc/tegra/core-power-domain.c
> @@ -0,0 +1,125 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * NVIDIA Tegra SoC Core Power Domain Driver
> + */
> +
> +#include <linux/of_device.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_domain.h>
> +#include <linux/pm_opp.h>
> +#include <linux/slab.h>
> +
> +#include <soc/tegra/common.h>
> +
> +static struct lock_class_key tegra_core_domain_lock_class;
> +static bool tegra_core_domain_state_synced;
> +
> +static int tegra_genpd_set_performance_state(struct generic_pm_domain *genpd,
> + unsigned int level)
> +{
> + struct dev_pm_opp *opp;
> + int err;
> +
> + opp = dev_pm_opp_find_level_ceil(&genpd->dev, &level);
> + if (IS_ERR(opp)) {
> + dev_err(&genpd->dev, "failed to find OPP for level %u: %pe\n",
> + level, opp);
> + return PTR_ERR(opp);
> + }
> +
> + err = dev_pm_opp_set_voltage(&genpd->dev, opp);
> + dev_pm_opp_put(opp);
> +
> + if (err) {
> + dev_err(&genpd->dev, "failed to set voltage to %duV: %d\n",
> + level, err);
> + return err;
> + }
> +
> + return 0;
> +}
> +
> +static unsigned int
> +tegra_genpd_opp_to_performance_state(struct generic_pm_domain *genpd,
> + struct dev_pm_opp *opp)
> +{
> + return dev_pm_opp_get_level(opp);
> +}
> +
> +static int tegra_core_domain_probe(struct platform_device *pdev)
> +{
> + struct generic_pm_domain *genpd;
> + struct opp_table *opp_table;
> + const char *rname = "power";
> + int err;
> +
> + genpd = devm_kzalloc(&pdev->dev, sizeof(*genpd), GFP_KERNEL);
> + if (!genpd)
> + return -ENOMEM;
> +
> + genpd->name = pdev->dev.of_node->name;
> + genpd->set_performance_state = tegra_genpd_set_performance_state;
> + genpd->opp_to_performance_state = tegra_genpd_opp_to_performance_state;
> +
> + opp_table = devm_pm_opp_set_regulators(&pdev->dev, &rname, 1);
> + if (IS_ERR(opp_table))
> + return dev_err_probe(&pdev->dev, PTR_ERR(opp_table),
> + "failed to set OPP regulator\n");
> +
> + err = pm_genpd_init(genpd, NULL, false);
> + if (err) {
> + dev_err(&pdev->dev, "failed to init genpd: %d\n", err);
> + return err;
> + }
> +
> + /*
> + * We have a "PMC -> Core" hierarchy of the power domains where
> + * PMC needs to resume and change performance (voltage) of the
> + * Core domain from the PMC GENPD on/off callbacks, hence we need
> + * to annotate the lock in order to remove confusion from the
> + * lockdep checker when a nested access happens.
> + */
> + lockdep_set_class(&genpd->mlock, &tegra_core_domain_lock_class);
> +
> + err = of_genpd_add_provider_simple(pdev->dev.of_node, genpd);
> + if (err) {
> + dev_err(&pdev->dev, "failed to add genpd: %d\n", err);
> + goto remove_genpd;
> + }
> +
> + return 0;
> +
> +remove_genpd:
> + pm_genpd_remove(genpd);
> +
> + return err;
> +}
> +
> +bool tegra_soc_core_domain_state_synced(void)
> +{
> + return tegra_core_domain_state_synced;
> +}
> +
> +static void tegra_core_domain_sync_state(struct device *dev)
> +{
> + tegra_core_domain_state_synced = true;
> +
> + dev_pm_opp_sync_regulators(dev);
> +}
> +
> +static const struct of_device_id tegra_core_domain_match[] = {
> + { .compatible = "nvidia,tegra20-core-domain", },
> + { .compatible = "nvidia,tegra30-core-domain", },
> + { }
> +};
> +
> +static struct platform_driver tegra_core_domain_driver = {
> + .driver = {
> + .name = "tegra-core-power",
> + .of_match_table = tegra_core_domain_match,
> + .suppress_bind_attrs = true,
> + .sync_state = tegra_core_domain_sync_state,
> + },
> + .probe = tegra_core_domain_probe,
> +};
> +builtin_platform_driver(tegra_core_domain_driver);
> diff --git a/include/soc/tegra/common.h b/include/soc/tegra/common.h
> index 57b56793a9e5..6c2ccbbbf073 100644
> --- a/include/soc/tegra/common.h
> +++ b/include/soc/tegra/common.h
> @@ -27,6 +27,7 @@ struct tegra_core_opp_params {
>
> #ifdef CONFIG_ARCH_TEGRA
> bool soc_is_tegra(void);
> +bool tegra_soc_core_domain_state_synced(void);
> int devm_tegra_core_dev_init_opp_table(struct device *dev,
> struct tegra_core_opp_params *cfg);
> #else
> @@ -35,6 +36,11 @@ static inline bool soc_is_tegra(void)
> return false;
> }
>
> +static inline bool tegra_soc_core_domain_state_synced(void)
> +{
> + return false;
> +}
> +
> static inline int
> devm_tegra_core_dev_init_opp_table(struct device *dev,
> struct tegra_core_opp_params *cfg)
> --
> 2.29.2
>

2021-01-12 14:01:53

by Ulf Hansson

[permalink] [raw]
Subject: Re: [PATCH v2 31/48] soc/tegra: regulators: Support Core domain state syncing

On Thu, 17 Dec 2020 at 19:07, Dmitry Osipenko <[email protected]> wrote:
>
> The core voltage shall not drop until state of Core domain is synced,
> i.e. all device drivers that use Core domain are loaded and ready.
>
> Support Core domain state syncing. The Core domain driver invokes the
> core-regulator voltage syncing once the state of domain is synced, at
> this point the Core voltage is allowed to go lower.
>
> Signed-off-by: Dmitry Osipenko <[email protected]>

This looks reasonable to me, feel free to add:

Reviewed-by: Ulf Hansson <[email protected]>

Kind regards
Uffe


> ---
> drivers/soc/tegra/regulators-tegra20.c | 19 ++++++++++++++++++-
> drivers/soc/tegra/regulators-tegra30.c | 18 +++++++++++++++++-
> 2 files changed, 35 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/soc/tegra/regulators-tegra20.c b/drivers/soc/tegra/regulators-tegra20.c
> index 367a71a3cd10..e2c11d442591 100644
> --- a/drivers/soc/tegra/regulators-tegra20.c
> +++ b/drivers/soc/tegra/regulators-tegra20.c
> @@ -16,6 +16,8 @@
> #include <linux/regulator/driver.h>
> #include <linux/regulator/machine.h>
>
> +#include <soc/tegra/common.h>
> +
> struct tegra_regulator_coupler {
> struct regulator_coupler coupler;
> struct regulator_dev *core_rdev;
> @@ -38,6 +40,21 @@ static int tegra20_core_limit(struct tegra_regulator_coupler *tegra,
> int core_cur_uV;
> int err;
>
> + /*
> + * Tegra20 SoC has critical DVFS-capable devices that are
> + * permanently-active or active at a boot time, like EMC
> + * (DRAM controller) or Display controller for example.
> + *
> + * The voltage of a CORE SoC power domain shall not be dropped below
> + * a minimum level, which is determined by device's clock rate.
> + * This means that we can't fully allow CORE voltage scaling until
> + * the state of all DVFS-critical CORE devices is synced.
> + */
> + if (tegra_soc_core_domain_state_synced()) {
> + pr_info_once("voltage state synced\n");
> + return 0;
> + }
> +
> if (tegra->core_min_uV > 0)
> return tegra->core_min_uV;
>
> @@ -58,7 +75,7 @@ static int tegra20_core_limit(struct tegra_regulator_coupler *tegra,
> */
> tegra->core_min_uV = core_max_uV;
>
> - pr_info("core minimum voltage limited to %duV\n", tegra->core_min_uV);
> + pr_info("core voltage initialized to %duV\n", tegra->core_min_uV);
>
> return tegra->core_min_uV;
> }
> diff --git a/drivers/soc/tegra/regulators-tegra30.c b/drivers/soc/tegra/regulators-tegra30.c
> index 0e776b20f625..42d675b79fa3 100644
> --- a/drivers/soc/tegra/regulators-tegra30.c
> +++ b/drivers/soc/tegra/regulators-tegra30.c
> @@ -16,6 +16,7 @@
> #include <linux/regulator/driver.h>
> #include <linux/regulator/machine.h>
>
> +#include <soc/tegra/common.h>
> #include <soc/tegra/fuse.h>
>
> struct tegra_regulator_coupler {
> @@ -39,6 +40,21 @@ static int tegra30_core_limit(struct tegra_regulator_coupler *tegra,
> int core_cur_uV;
> int err;
>
> + /*
> + * Tegra30 SoC has critical DVFS-capable devices that are
> + * permanently-active or active at a boot time, like EMC
> + * (DRAM controller) or Display controller for example.
> + *
> + * The voltage of a CORE SoC power domain shall not be dropped below
> + * a minimum level, which is determined by device's clock rate.
> + * This means that we can't fully allow CORE voltage scaling until
> + * the state of all DVFS-critical CORE devices is synced.
> + */
> + if (tegra_soc_core_domain_state_synced()) {
> + pr_info_once("voltage state synced\n");
> + return 0;
> + }
> +
> if (tegra->core_min_uV > 0)
> return tegra->core_min_uV;
>
> @@ -59,7 +75,7 @@ static int tegra30_core_limit(struct tegra_regulator_coupler *tegra,
> */
> tegra->core_min_uV = core_max_uV;
>
> - pr_info("core minimum voltage limited to %duV\n", tegra->core_min_uV);
> + pr_info("core voltage initialized to %duV\n", tegra->core_min_uV);
>
> return tegra->core_min_uV;
> }
> --
> 2.29.2
>

2021-01-12 14:20:24

by Ulf Hansson

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

- trimmed cc-list

On Thu, 17 Dec 2020 at 19:08, 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]>
> Tested-by: Nicolas Chauvet <[email protected]>
> Signed-off-by: Dmitry Osipenko <[email protected]>
> ---
> drivers/gpu/drm/tegra/dc.c | 66 +++++++++++++++++++++++++++++++++++++-
> 1 file changed, 65 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/gpu/drm/tegra/dc.c b/drivers/gpu/drm/tegra/dc.c
> index b6676f1fe358..105ad786e432 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>
> @@ -1699,6 +1702,48 @@ 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;
> +
> + /* 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);
> +
> + /* -ENOENT means that this device-tree doesn't have OPP table */
> + if (opp == ERR_PTR(-ENOENT))
> + return;
> +
> + if (IS_ERR(opp)) {
> + dev_err(dc->dev, "failed to find OPP for %luHz: %pe\n",
> + rate, opp);
> + return;
> + }
> +
> + pstate = dev_pm_opp_get_voltage(opp);
> + 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);

As you state above, in general we should not need to call the
dev_pm_genpd_set_performance_state() directly for the consumer driver.

Even if this looks like a special case to me, I would appreciate a
confirmation from Viresh that this is the way he also would like to
move forward from the opp library perspective.

> + if (err)
> + dev_err(dc->dev, "failed to set power domain state to %lu: %d\n",
> + pstate, err);
> +}
> +

[...]

Kind regards
Uffe

2021-01-12 16:25:11

by Dmitry Osipenko

[permalink] [raw]
Subject: Re: [PATCH v2 29/48] soc/tegra: pmc: Link domains to the parent Core domain

12.01.2021 16:30, Ulf Hansson пишет:
> - trimmed cc-list
>
> On Thu, 17 Dec 2020 at 19:07, Dmitry Osipenko <[email protected]> wrote:
>>
>> The Core domain is a parent of PMC power domains, hence PMC domains
>> should be set up as a sub-domains of the parent (Core) domain if
>> "power-domains" phandle presents in a device-tree node of PMC domain.
>>
>> This allows to propagate GENPD performance changes to the parent Core
>> domain if performance change is applied to PMC domain.
>>
>> Signed-off-by: Dmitry Osipenko <[email protected]>
>> ---
>> drivers/soc/tegra/pmc.c | 19 +++++++++++++++++++
>> 1 file changed, 19 insertions(+)
>>
>> diff --git a/drivers/soc/tegra/pmc.c b/drivers/soc/tegra/pmc.c
>> index 4f96dc7745c4..1a659d1c06d7 100644
>> --- a/drivers/soc/tegra/pmc.c
>> +++ b/drivers/soc/tegra/pmc.c
>> @@ -1236,6 +1236,7 @@ static int tegra_powergate_add(struct tegra_pmc *pmc, struct device_node *np)
>> static int tegra_powergate_init(struct tegra_pmc *pmc,
>> struct device_node *parent)
>> {
>> + struct of_phandle_args child_args, parent_args;
>> struct device_node *np, *child;
>> int err = 0;
>>
>> @@ -1249,6 +1250,24 @@ static int tegra_powergate_init(struct tegra_pmc *pmc,
>> of_node_put(child);
>> break;
>> }
>> +
>> + if (of_parse_phandle_with_args(child, "power-domains",
>> + "#power-domain-cells",
>> + 0, &parent_args))
>> + continue;
>> +
>> + child_args.np = child;
>> + child_args.args_count = 0;
>> +
>> + err = of_genpd_add_subdomain(&parent_args, &child_args);
>> + of_node_put(parent_args.np);
>> + if (err) {
>> + if (err == -ENOENT)
>> + err = -EPROBE_DEFER;
>
> Okay. So this special error treatment is needed because
> of_genpd_add_subdomain may return -ENOENT, in case the providers for
> the parent-domain and child-domain haven't been registered yet.
>
> I suggest we move this into of_genpd_add_subdomain() instead, thus
> letting it return -EPROBE_DEFER when there are parent/child nodes
> specified in DT, but the providers are lacking.

Alright