2023-01-26 06:34:26

by Samuel Holland

[permalink] [raw]
Subject: [PATCH v2 0/3] Allwinner D1 power domain support

This series adds support for the power controller found in D1 and other
recent Allwinner SoCs. There is no first-party documentation, but there
are a couple of vendor drivers for different hardware revisions[1][2],
and the register definitions were easy to verify empirically.

I have tested this driver on D1 with the video engine.

[1]: https://github.com/mangopi-sbc/tina-linux-5.4/blob/main/drivers/soc/sunxi/gpu_domain.c
[2]: https://github.com/mangopi-sbc/tina-linux-5.4/blob/main/drivers/soc/sunxi/pm_domains.c

Changes in v2:
- Removed quotes from "PPU"
- Fixed indentation in binding example
- Removed possibly misleading comment from the Kconfig description
- Include a patch adding the device tree node

Samuel Holland (3):
dt-bindings: power: Add Allwinner D1 PPU
soc: sunxi: Add Allwinner D1 PPU driver
riscv: dts: allwinner: d1: Add power controller node

.../power/allwinner,sun20i-d1-ppu.yaml | 54 +++++
.../boot/dts/allwinner/sunxi-d1s-t113.dtsi | 8 +
drivers/soc/sunxi/Kconfig | 8 +
drivers/soc/sunxi/Makefile | 1 +
drivers/soc/sunxi/sun20i-ppu.c | 207 ++++++++++++++++++
.../power/allwinner,sun20i-d1-ppu.h | 10 +
6 files changed, 288 insertions(+)
create mode 100644 Documentation/devicetree/bindings/power/allwinner,sun20i-d1-ppu.yaml
create mode 100644 drivers/soc/sunxi/sun20i-ppu.c
create mode 100644 include/dt-bindings/power/allwinner,sun20i-d1-ppu.h

--
2.37.4



2023-01-26 06:34:31

by Samuel Holland

[permalink] [raw]
Subject: [PATCH v2 1/3] dt-bindings: power: Add Allwinner D1 PPU

The Allwinner D1 family of SoCs contain a PPU power domain controller
separate from the PRCM. It can power down the video engine and DSP, and
it contains special logic for hardware-assisted CPU idle. Other recent
Allwinner SoCs (e.g. TV303) have a PPU with a different set of domains.

Reviewed-by: Krzysztof Kozlowski <[email protected]>
Signed-off-by: Samuel Holland <[email protected]>
---

Changes in v2:
- Removed quotes from "PPU"
- Fixed indentation in binding example

.../power/allwinner,sun20i-d1-ppu.yaml | 54 +++++++++++++++++++
.../power/allwinner,sun20i-d1-ppu.h | 10 ++++
2 files changed, 64 insertions(+)
create mode 100644 Documentation/devicetree/bindings/power/allwinner,sun20i-d1-ppu.yaml
create mode 100644 include/dt-bindings/power/allwinner,sun20i-d1-ppu.h

diff --git a/Documentation/devicetree/bindings/power/allwinner,sun20i-d1-ppu.yaml b/Documentation/devicetree/bindings/power/allwinner,sun20i-d1-ppu.yaml
new file mode 100644
index 000000000000..46e2647a5d72
--- /dev/null
+++ b/Documentation/devicetree/bindings/power/allwinner,sun20i-d1-ppu.yaml
@@ -0,0 +1,54 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/power/allwinner,sun20i-d1-ppu.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Allwinner SoCs PPU power domain controller
+
+maintainers:
+ - Samuel Holland <[email protected]>
+
+description:
+ D1 and related SoCs contain a power domain controller for the CPUs, GPU, and
+ video-related hardware.
+
+properties:
+ compatible:
+ enum:
+ - allwinner,sun20i-d1-ppu
+
+ reg:
+ maxItems: 1
+
+ clocks:
+ description: Bus Clock
+ maxItems: 1
+
+ resets:
+ maxItems: 1
+
+ '#power-domain-cells':
+ const: 1
+
+required:
+ - compatible
+ - reg
+ - clocks
+ - resets
+ - '#power-domain-cells'
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/clock/sun20i-d1-r-ccu.h>
+ #include <dt-bindings/reset/sun20i-d1-r-ccu.h>
+
+ ppu: power-controller@7001000 {
+ compatible = "allwinner,sun20i-d1-ppu";
+ reg = <0x7001000 0x1000>;
+ clocks = <&r_ccu CLK_BUS_R_PPU>;
+ resets = <&r_ccu RST_BUS_R_PPU>;
+ #power-domain-cells = <1>;
+ };
diff --git a/include/dt-bindings/power/allwinner,sun20i-d1-ppu.h b/include/dt-bindings/power/allwinner,sun20i-d1-ppu.h
new file mode 100644
index 000000000000..23cfb57256d6
--- /dev/null
+++ b/include/dt-bindings/power/allwinner,sun20i-d1-ppu.h
@@ -0,0 +1,10 @@
+/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */
+
+#ifndef _DT_BINDINGS_POWER_SUN20I_D1_PPU_H_
+#define _DT_BINDINGS_POWER_SUN20I_D1_PPU_H_
+
+#define PD_CPU 0
+#define PD_VE 1
+#define PD_DSP 2
+
+#endif /* _DT_BINDINGS_POWER_SUN20I_D1_PPU_H_ */
--
2.37.4


2023-01-26 06:34:38

by Samuel Holland

[permalink] [raw]
Subject: [PATCH v2 2/3] soc: sunxi: Add Allwinner D1 PPU driver

The PPU contains a series of identical MMIO register ranges, one for
each power domain. Each range contains control/status bits for a clock
gate, reset line, output gates, and a power switch. (The clock and reset
are separate from, and in addition to, the bits in the CCU.) It also
contains a hardware power sequence engine to control the other bits.

Acked-by: Jernej Skrabec <[email protected]>
Signed-off-by: Samuel Holland <[email protected]>
---

Changes in v2:
- Removed possibly misleading comment from the Kconfig description

drivers/soc/sunxi/Kconfig | 8 ++
drivers/soc/sunxi/Makefile | 1 +
drivers/soc/sunxi/sun20i-ppu.c | 207 +++++++++++++++++++++++++++++++++
3 files changed, 216 insertions(+)
create mode 100644 drivers/soc/sunxi/sun20i-ppu.c

diff --git a/drivers/soc/sunxi/Kconfig b/drivers/soc/sunxi/Kconfig
index 8aecbc9b1976..29e9ba2d520d 100644
--- a/drivers/soc/sunxi/Kconfig
+++ b/drivers/soc/sunxi/Kconfig
@@ -19,3 +19,11 @@ config SUNXI_SRAM
Say y here to enable the SRAM controller support. This
device is responsible on mapping the SRAM in the sunXi SoCs
whether to the CPU/DMA, or to the devices.
+
+config SUN20I_PPU
+ bool "Allwinner D1 PPU power domain driver"
+ depends on ARCH_SUNXI || COMPILE_TEST
+ select PM_GENERIC_DOMAINS
+ help
+ Say y to enable the PPU power domain driver. This saves power
+ when certain peripherals, such as the video engine, are idle.
diff --git a/drivers/soc/sunxi/Makefile b/drivers/soc/sunxi/Makefile
index 549159571d4f..90ff2ebe7655 100644
--- a/drivers/soc/sunxi/Makefile
+++ b/drivers/soc/sunxi/Makefile
@@ -1,3 +1,4 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_SUNXI_MBUS) += sunxi_mbus.o
obj-$(CONFIG_SUNXI_SRAM) += sunxi_sram.o
+obj-$(CONFIG_SUN20I_PPU) += sun20i-ppu.o
diff --git a/drivers/soc/sunxi/sun20i-ppu.c b/drivers/soc/sunxi/sun20i-ppu.c
new file mode 100644
index 000000000000..98cb41d36560
--- /dev/null
+++ b/drivers/soc/sunxi/sun20i-ppu.c
@@ -0,0 +1,207 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/pm_domain.h>
+#include <linux/reset.h>
+
+#define PD_STATE_ON 1
+#define PD_STATE_OFF 2
+
+#define PD_RSTN_REG 0x00
+#define PD_CLK_GATE_REG 0x04
+#define PD_PWROFF_GATE_REG 0x08
+#define PD_PSW_ON_REG 0x0c
+#define PD_PSW_OFF_REG 0x10
+#define PD_PSW_DELAY_REG 0x14
+#define PD_OFF_DELAY_REG 0x18
+#define PD_ON_DELAY_REG 0x1c
+#define PD_COMMAND_REG 0x20
+#define PD_STATUS_REG 0x24
+#define PD_STATUS_COMPLETE BIT(1)
+#define PD_STATUS_BUSY BIT(3)
+#define PD_STATUS_STATE GENMASK(17, 16)
+#define PD_ACTIVE_CTRL_REG 0x2c
+#define PD_GATE_STATUS_REG 0x30
+#define PD_RSTN_STATUS BIT(0)
+#define PD_CLK_GATE_STATUS BIT(1)
+#define PD_PWROFF_GATE_STATUS BIT(2)
+#define PD_PSW_STATUS_REG 0x34
+
+#define PD_REGS_SIZE 0x80
+
+struct sun20i_ppu_desc {
+ const char *const *names;
+ unsigned int num_domains;
+};
+
+struct sun20i_ppu_pd {
+ struct generic_pm_domain genpd;
+ void __iomem *base;
+};
+
+#define to_sun20i_ppu_pd(_genpd) \
+ container_of(_genpd, struct sun20i_ppu_pd, genpd)
+
+static bool sun20i_ppu_pd_is_on(const struct sun20i_ppu_pd *pd)
+{
+ u32 status = readl(pd->base + PD_STATUS_REG);
+
+ return FIELD_GET(PD_STATUS_STATE, status) == PD_STATE_ON;
+}
+
+static int sun20i_ppu_pd_set_power(const struct sun20i_ppu_pd *pd, bool power_on)
+{
+ u32 state, status;
+ int ret;
+
+ if (sun20i_ppu_pd_is_on(pd) == power_on)
+ return 0;
+
+ /* Wait for the power controller to be idle. */
+ ret = readl_poll_timeout(pd->base + PD_STATUS_REG, status,
+ !(status & PD_STATUS_BUSY), 100, 1000);
+ if (ret)
+ return ret;
+
+ state = power_on ? PD_STATE_ON : PD_STATE_OFF;
+ writel(state, pd->base + PD_COMMAND_REG);
+
+ /* Wait for the state transition to complete. */
+ ret = readl_poll_timeout(pd->base + PD_STATUS_REG, status,
+ FIELD_GET(PD_STATUS_STATE, status) == state &&
+ (status & PD_STATUS_COMPLETE), 100, 1000);
+ if (ret)
+ return ret;
+
+ /* Clear the completion flag. */
+ writel(status, pd->base + PD_STATUS_REG);
+
+ return 0;
+}
+
+static int sun20i_ppu_pd_power_on(struct generic_pm_domain *genpd)
+{
+ const struct sun20i_ppu_pd *pd = to_sun20i_ppu_pd(genpd);
+
+ return sun20i_ppu_pd_set_power(pd, true);
+}
+
+static int sun20i_ppu_pd_power_off(struct generic_pm_domain *genpd)
+{
+ const struct sun20i_ppu_pd *pd = to_sun20i_ppu_pd(genpd);
+
+ return sun20i_ppu_pd_set_power(pd, false);
+}
+
+static int sun20i_ppu_probe(struct platform_device *pdev)
+{
+ const struct sun20i_ppu_desc *desc;
+ struct device *dev = &pdev->dev;
+ struct genpd_onecell_data *ppu;
+ struct sun20i_ppu_pd *pds;
+ struct reset_control *rst;
+ void __iomem *base;
+ struct clk *clk;
+ int ret;
+
+ desc = of_device_get_match_data(dev);
+ if (!desc)
+ return -EINVAL;
+
+ pds = devm_kcalloc(dev, desc->num_domains, sizeof(*pds), GFP_KERNEL);
+ if (!pds)
+ return -ENOMEM;
+
+ ppu = devm_kzalloc(dev, sizeof(*ppu), GFP_KERNEL);
+ if (!ppu)
+ return -ENOMEM;
+
+ ppu->domains = devm_kcalloc(dev, desc->num_domains,
+ sizeof(*ppu->domains), GFP_KERNEL);
+ if (!ppu->domains)
+ return -ENOMEM;
+
+ ppu->num_domains = desc->num_domains;
+ platform_set_drvdata(pdev, ppu);
+
+ base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(base))
+ return PTR_ERR(base);
+
+ clk = devm_clk_get_enabled(dev, NULL);
+ if (IS_ERR(clk))
+ return PTR_ERR(clk);
+
+ rst = devm_reset_control_get_exclusive(dev, NULL);
+ if (IS_ERR(rst))
+ return PTR_ERR(rst);
+
+ ret = reset_control_deassert(rst);
+ if (ret)
+ return ret;
+
+ for (unsigned int i = 0; i < ppu->num_domains; ++i) {
+ struct sun20i_ppu_pd *pd = &pds[i];
+
+ pd->genpd.name = desc->names[i];
+ pd->genpd.power_off = sun20i_ppu_pd_power_off;
+ pd->genpd.power_on = sun20i_ppu_pd_power_on;
+ pd->base = base + PD_REGS_SIZE * i;
+
+ ret = pm_genpd_init(&pd->genpd, NULL, sun20i_ppu_pd_is_on(pd));
+ if (ret) {
+ dev_warn(dev, "Failed to add '%s' domain: %d\n",
+ pd->genpd.name, ret);
+ continue;
+ }
+
+ ppu->domains[i] = &pd->genpd;
+ }
+
+ ret = of_genpd_add_provider_onecell(dev->of_node, ppu);
+ if (ret)
+ dev_warn(dev, "Failed to add provider: %d\n", ret);
+
+ return 0;
+}
+
+static const char *const sun20i_d1_ppu_pd_names[] = {
+ "CPU",
+ "VE",
+ "DSP",
+};
+
+static const struct sun20i_ppu_desc sun20i_d1_ppu_desc = {
+ .names = sun20i_d1_ppu_pd_names,
+ .num_domains = ARRAY_SIZE(sun20i_d1_ppu_pd_names),
+};
+
+static const struct of_device_id sun20i_ppu_of_match[] = {
+ {
+ .compatible = "allwinner,sun20i-d1-ppu",
+ .data = &sun20i_d1_ppu_desc,
+ },
+ { }
+};
+MODULE_DEVICE_TABLE(of, sun20i_ppu_of_match);
+
+static struct platform_driver sun20i_ppu_driver = {
+ .probe = sun20i_ppu_probe,
+ .driver = {
+ .name = "sun20i-ppu",
+ .of_match_table = sun20i_ppu_of_match,
+ /* Power domains cannot be removed while they are in use. */
+ .suppress_bind_attrs = true,
+ },
+};
+module_platform_driver(sun20i_ppu_driver);
+
+MODULE_AUTHOR("Samuel Holland <[email protected]>");
+MODULE_DESCRIPTION("Allwinner D1 PPU power domain driver");
+MODULE_LICENSE("GPL");
--
2.37.4


2023-01-26 06:34:44

by Samuel Holland

[permalink] [raw]
Subject: [PATCH v2 3/3] riscv: dts: allwinner: d1: Add power controller node

The Allwinner D1 family of SoCs contain a PPU power domain controller
separate from the PRCM. It can power down the video engine and DSP, and
it contains special logic for hardware-assisted CPU idle.

Signed-off-by: Samuel Holland <[email protected]>
---

Changes in v2:
- Include a patch adding the device tree node

arch/riscv/boot/dts/allwinner/sunxi-d1s-t113.dtsi | 8 ++++++++
1 file changed, 8 insertions(+)

diff --git a/arch/riscv/boot/dts/allwinner/sunxi-d1s-t113.dtsi b/arch/riscv/boot/dts/allwinner/sunxi-d1s-t113.dtsi
index 3723612b1fd8..6fadcee7800f 100644
--- a/arch/riscv/boot/dts/allwinner/sunxi-d1s-t113.dtsi
+++ b/arch/riscv/boot/dts/allwinner/sunxi-d1s-t113.dtsi
@@ -799,6 +799,14 @@ tcon_tv0_out_tcon_top_hdmi: endpoint {
};
};

+ ppu: power-controller@7001000 {
+ compatible = "allwinner,sun20i-d1-ppu";
+ reg = <0x7001000 0x1000>;
+ clocks = <&r_ccu CLK_BUS_R_PPU>;
+ resets = <&r_ccu RST_BUS_R_PPU>;
+ #power-domain-cells = <1>;
+ };
+
r_ccu: clock-controller@7010000 {
compatible = "allwinner,sun20i-d1-r-ccu";
reg = <0x7010000 0x400>;
--
2.37.4


2023-01-27 22:17:42

by Jernej Škrabec

[permalink] [raw]
Subject: Re: [PATCH v2 3/3] riscv: dts: allwinner: d1: Add power controller node

Dne četrtek, 26. januar 2023 ob 07:34:19 CET je Samuel Holland napisal(a):
> The Allwinner D1 family of SoCs contain a PPU power domain controller
> separate from the PRCM. It can power down the video engine and DSP, and
> it contains special logic for hardware-assisted CPU idle.
>
> Signed-off-by: Samuel Holland <[email protected]>

Acked-by: Jernej Skrabec <[email protected]>

Best regards,
Jernej

> ---
>
> Changes in v2:
> - Include a patch adding the device tree node
>
> arch/riscv/boot/dts/allwinner/sunxi-d1s-t113.dtsi | 8 ++++++++
> 1 file changed, 8 insertions(+)
>
> diff --git a/arch/riscv/boot/dts/allwinner/sunxi-d1s-t113.dtsi
> b/arch/riscv/boot/dts/allwinner/sunxi-d1s-t113.dtsi index
> 3723612b1fd8..6fadcee7800f 100644
> --- a/arch/riscv/boot/dts/allwinner/sunxi-d1s-t113.dtsi
> +++ b/arch/riscv/boot/dts/allwinner/sunxi-d1s-t113.dtsi
> @@ -799,6 +799,14 @@ tcon_tv0_out_tcon_top_hdmi: endpoint {
> };
> };
>
> + ppu: power-controller@7001000 {
> + compatible = "allwinner,sun20i-d1-ppu";
> + reg = <0x7001000 0x1000>;
> + clocks = <&r_ccu CLK_BUS_R_PPU>;
> + resets = <&r_ccu RST_BUS_R_PPU>;
> + #power-domain-cells = <1>;
> + };
> +
> r_ccu: clock-controller@7010000 {
> compatible = "allwinner,sun20i-d1-r-ccu";
> reg = <0x7010000 0x400>;