From 034dd340b08be1f2f0477ad16131d609f9dbd53c Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Sun, 30 Aug 2026 09:22:00 -0700 Subject: Merge tag 'trace-v7.3-2' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace Pull tracing fixes from Steven Rostedt: - Fix error output of boot instance creation failure Currently if a boot instance creation fails, instead of printing out the name of the instance that failed, it prints "(null)". That is because it prints "cur_str" that had already been processed by strsep(). Print the saved name instead. While at it, print the error code of the failure. - Fix use-after-free for same named historgrams Histograms can be named so that they can be used in multiple events. But if the named histogram has a variable attached, the second event that uses the named histogram which duplicates it and needs to free the original after duplication leaves the old variable in place and still visible. If another histogram uses than variable, it will use the stale one which will try to reference the freed duplicate histogram and crash the kernel. Free the duplicate variables along with the duplicated histogram data. - Check return value of kthread_run() in event self test The events self tests uses a kthread for testing but does not check if it succeeded in creating a kthread. If the kthread creation were to fail, the code will still try to call kthread_stop() on the error returned. - Fix race between reading trace_pipe and updating subbuffer size If a user is reading the trace_pipe file at the same time they update the ring buffer sub-buffer size, can cause the trace_pipe read to read stale data. Add trace_access_lock() around updating the ring buffer sub-buffer size. - Fix eventfs_inode on failure path in creation of the events directory In the creation of the "events" directory, if after allocating the eventfs_inode a failure is detected, it calls cleanup_ei() which calls free_ei(). The free_ei() will test if eventfs_inode being freed has no children. It is a bug if it does. But on the failure case of the creation of the "events" directory, the children lists have not yet been initialized and the free will trigger a warning because list_empty() on an uninitialized list returns false. Move the initialization into init_ei() where it makes more sense and makes sure that a created eventfs_inode has its lists initialized upon creation. - Check return value of kthread_run() in ftrace direct sample code The sample code that shows how to use the ftrace direct calls does not test the return of kthread_run() to see if it succeeds. Return a failure if the kthread_run() doesn't succeed. - Clear user events state on fork in case of alloc failure On fork, the child gets a pointer to the parent's user events state. It makes a copy of it then updates the child's pointer to it. But if the allocation fails, the duplication function leaves the child with a pointer to its parent's descriptor. When the child cleans up its data, it will free the parent's descriptor while the parent is still using it. In the duplication function, set the child's user_event_mm to NULL before testing if the allocation succeeded, and when it exits it will not free the parent's descriptor. - Fix retry exhaustion in simple ring buffer reader swap simple_ring_buffer_swap_reader_page() starts with retry set to 8 and post-decrements it only after a failed link replacement. On the final attempt, a successful replacement leaves retry at zero, while a failed replacement leaves it at -1. But the check for success expects the retry value to be non-zero and exits with an error on zero. This is the opposite result. Fix it. - Fail nicely when the remote swap_reader_page() returns an error Currently, if the swap_reader_page() of a remote buffer fails, it triggers a WARN_ON_ONCE() and continues normally. Instead, have it exit with an error and a pr_warn() print instead of a full WARNING. * tag 'trace-v7.3-2' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace: ring-buffer: Stop remote reader update when page swap fails tracing: Fix retry exhaustion in simple ring buffer reader swap tracing/user_events: Clear copied tracing state before fork duplication samples/ftrace: Fix kthread_stop() on ERR_PTR in ftrace-direct-multi-modify samples/ftrace: Fix kthread_stop() on ERR_PTR in ftrace-direct-modify eventfs: Initialize ei->children and ei->list in init_ei() tracing: Fix use-after-free in trace_pipe read on sub-buffer order change tracing: Fix crash passing ERR_PTR to kthread_stop() tracing: Fix use-after-free with same-name named triggers tracing: Fix logged instance name on creation failure --- drivers/pmdomain/sunxi/Kconfig | 33 ++++ drivers/pmdomain/sunxi/Makefile | 4 + drivers/pmdomain/sunxi/sun20i-ppu.c | 239 ++++++++++++++++++++++++ drivers/pmdomain/sunxi/sun50i-h6-prcm-ppu.c | 208 +++++++++++++++++++++ drivers/pmdomain/sunxi/sun55i-pck600.c | 270 ++++++++++++++++++++++++++++ 5 files changed, 754 insertions(+) create mode 100644 drivers/pmdomain/sunxi/Kconfig create mode 100644 drivers/pmdomain/sunxi/Makefile create mode 100644 drivers/pmdomain/sunxi/sun20i-ppu.c create mode 100644 drivers/pmdomain/sunxi/sun50i-h6-prcm-ppu.c create mode 100644 drivers/pmdomain/sunxi/sun55i-pck600.c (limited to 'drivers/pmdomain/sunxi') diff --git a/drivers/pmdomain/sunxi/Kconfig b/drivers/pmdomain/sunxi/Kconfig new file mode 100644 index 000000000..858446594 --- /dev/null +++ b/drivers/pmdomain/sunxi/Kconfig @@ -0,0 +1,33 @@ +# SPDX-License-Identifier: GPL-2.0-only + +config SUN20I_PPU + tristate "Allwinner D1 PPU power domain driver" + depends on ARCH_SUNXI || COMPILE_TEST + depends on PM + default ARCH_SUNXI + select PM_GENERIC_DOMAINS + help + Say y to enable the PPU power domain driver. This is required + to enable power to certain peripherals, such as the display + engine. + +config SUN50I_H6_PRCM_PPU + tristate "Allwinner H6 PRCM power domain driver" + depends on ARCH_SUNXI || COMPILE_TEST + depends on PM + select PM_GENERIC_DOMAINS + help + Say y to enable the Allwinner H6/H616 PRCM power domain driver. + This is required to enable the Mali GPU in the H616 SoC, it is + optional for the H6. + +config SUN55I_PCK600 + tristate "Allwinner A523 PCK-600 power domain driver" + depends on ARCH_SUNXI || COMPILE_TEST + depends on PM + default ARCH_SUNXI + select PM_GENERIC_DOMAINS + help + Say y to enable the PCK-600 power domain driver. This is required + to enable power to certain peripherals, such as the display and + video engines. diff --git a/drivers/pmdomain/sunxi/Makefile b/drivers/pmdomain/sunxi/Makefile new file mode 100644 index 000000000..e344b232f --- /dev/null +++ b/drivers/pmdomain/sunxi/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only +obj-$(CONFIG_SUN20I_PPU) += sun20i-ppu.o +obj-$(CONFIG_SUN50I_H6_PRCM_PPU) += sun50i-h6-prcm-ppu.o +obj-$(CONFIG_SUN55I_PCK600) += sun55i-pck600.o diff --git a/drivers/pmdomain/sunxi/sun20i-ppu.c b/drivers/pmdomain/sunxi/sun20i-ppu.c new file mode 100644 index 000000000..b65876a68 --- /dev/null +++ b/drivers/pmdomain/sunxi/sun20i-ppu.c @@ -0,0 +1,239 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 char *const sun8i_v853_ppu_pd_names[] = { + "RISCV", + "NPU", + "VE", +}; + +static const struct sun20i_ppu_desc sun8i_v853_ppu_desc = { + .names = sun8i_v853_ppu_pd_names, + .num_domains = ARRAY_SIZE(sun8i_v853_ppu_pd_names), +}; + +static const char *const sun55i_a523_ppu_pd_names[] = { + "DSP", + "NPU", + "AUDIO", + "SRAM", + "RISCV", +}; + +static const struct sun20i_ppu_desc sun55i_a523_ppu_desc = { + .names = sun55i_a523_ppu_pd_names, + .num_domains = ARRAY_SIZE(sun55i_a523_ppu_pd_names), +}; + +static const struct of_device_id sun20i_ppu_of_match[] = { + { + .compatible = "allwinner,sun20i-d1-ppu", + .data = &sun20i_d1_ppu_desc, + }, + { + .compatible = "allwinner,sun8i-v853-ppu", + .data = &sun8i_v853_ppu_desc, + }, + { + .compatible = "allwinner,sun55i-a523-ppu", + .data = &sun55i_a523_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 "); +MODULE_DESCRIPTION("Allwinner D1 PPU power domain driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/pmdomain/sunxi/sun50i-h6-prcm-ppu.c b/drivers/pmdomain/sunxi/sun50i-h6-prcm-ppu.c new file mode 100644 index 000000000..d59644499 --- /dev/null +++ b/drivers/pmdomain/sunxi/sun50i-h6-prcm-ppu.c @@ -0,0 +1,208 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) Arm Ltd. 2024 + * + * Allwinner H6/H616 PRCM power domain driver. + * This covers a few registers inside the PRCM (Power Reset Clock Management) + * block that control some power rails, most prominently for the Mali GPU. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * The PRCM block covers multiple devices, starting with some clocks, + * then followed by the power rails. + * The clocks are covered by a different driver, so this driver's MMIO range + * starts later in the PRCM MMIO frame, not at the beginning of it. + * To keep the register offsets consistent with other PRCM documentation, + * express the registers relative to the beginning of the whole PRCM, and + * subtract the PPU offset this driver is bound to. + */ +#define PD_H6_PPU_OFFSET 0x250 +#define PD_H6_VDD_SYS_REG 0x250 +#define PD_H616_ANA_VDD_GATE BIT(4) +#define PD_H6_CPUS_VDD_GATE BIT(3) +#define PD_H6_AVCC_VDD_GATE BIT(2) +#define PD_H6_GPU_REG 0x254 +#define PD_H6_GPU_GATE BIT(0) + +struct sun50i_h6_ppu_pd { + struct generic_pm_domain genpd; + void __iomem *reg; + u32 gate_mask; + bool negated; +}; + +#define FLAG_PPU_ALWAYS_ON BIT(0) +#define FLAG_PPU_NEGATED BIT(1) + +struct sun50i_h6_ppu_desc { + const char *name; + u32 offset; + u32 mask; + unsigned int flags; +}; + +static const struct sun50i_h6_ppu_desc sun50i_h6_ppus[] = { + { "AVCC", PD_H6_VDD_SYS_REG, PD_H6_AVCC_VDD_GATE }, + { "CPUS", PD_H6_VDD_SYS_REG, PD_H6_CPUS_VDD_GATE }, + { "GPU", PD_H6_GPU_REG, PD_H6_GPU_GATE }, +}; +static const struct sun50i_h6_ppu_desc sun50i_h616_ppus[] = { + { "PLL", PD_H6_VDD_SYS_REG, PD_H6_AVCC_VDD_GATE, + FLAG_PPU_ALWAYS_ON | FLAG_PPU_NEGATED }, + { "ANA", PD_H6_VDD_SYS_REG, PD_H616_ANA_VDD_GATE, FLAG_PPU_ALWAYS_ON }, + { "GPU", PD_H6_GPU_REG, PD_H6_GPU_GATE, FLAG_PPU_NEGATED }, +}; + +struct sun50i_h6_ppu_data { + const struct sun50i_h6_ppu_desc *descs; + int nr_domains; +}; + +static const struct sun50i_h6_ppu_data sun50i_h6_ppu_data = { + .descs = sun50i_h6_ppus, + .nr_domains = ARRAY_SIZE(sun50i_h6_ppus), +}; + +static const struct sun50i_h6_ppu_data sun50i_h616_ppu_data = { + .descs = sun50i_h616_ppus, + .nr_domains = ARRAY_SIZE(sun50i_h616_ppus), +}; + +#define to_sun50i_h6_ppu_pd(_genpd) \ + container_of(_genpd, struct sun50i_h6_ppu_pd, genpd) + +static bool sun50i_h6_ppu_power_status(const struct sun50i_h6_ppu_pd *pd) +{ + bool bit = readl(pd->reg) & pd->gate_mask; + + return bit ^ pd->negated; +} + +static int sun50i_h6_ppu_pd_set_power(const struct sun50i_h6_ppu_pd *pd, + bool set_bit) +{ + u32 reg = readl(pd->reg); + + if (set_bit) + writel(reg | pd->gate_mask, pd->reg); + else + writel(reg & ~pd->gate_mask, pd->reg); + + return 0; +} + +static int sun50i_h6_ppu_pd_power_on(struct generic_pm_domain *genpd) +{ + const struct sun50i_h6_ppu_pd *pd = to_sun50i_h6_ppu_pd(genpd); + + return sun50i_h6_ppu_pd_set_power(pd, !pd->negated); +} + +static int sun50i_h6_ppu_pd_power_off(struct generic_pm_domain *genpd) +{ + const struct sun50i_h6_ppu_pd *pd = to_sun50i_h6_ppu_pd(genpd); + + return sun50i_h6_ppu_pd_set_power(pd, pd->negated); +} + +static int sun50i_h6_ppu_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct genpd_onecell_data *ppu; + struct sun50i_h6_ppu_pd *pds; + const struct sun50i_h6_ppu_data *data; + void __iomem *base; + int ret, i; + + data = of_device_get_match_data(dev); + if (!data) + return -EINVAL; + + pds = devm_kcalloc(dev, data->nr_domains, sizeof(*pds), GFP_KERNEL); + if (!pds) + return -ENOMEM; + + ppu = devm_kzalloc(dev, sizeof(*ppu), GFP_KERNEL); + if (!ppu) + return -ENOMEM; + + ppu->num_domains = data->nr_domains; + ppu->domains = devm_kcalloc(dev, data->nr_domains, + sizeof(*ppu->domains), GFP_KERNEL); + if (!ppu->domains) + return -ENOMEM; + + platform_set_drvdata(pdev, ppu); + + base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(base)) + return PTR_ERR(base); + + for (i = 0; i < data->nr_domains; i++) { + struct sun50i_h6_ppu_pd *pd = &pds[i]; + const struct sun50i_h6_ppu_desc *desc = &data->descs[i]; + + pd->genpd.name = desc->name; + pd->genpd.power_off = sun50i_h6_ppu_pd_power_off; + pd->genpd.power_on = sun50i_h6_ppu_pd_power_on; + if (desc->flags & FLAG_PPU_ALWAYS_ON) + pd->genpd.flags = GENPD_FLAG_ALWAYS_ON; + pd->negated = !!(desc->flags & FLAG_PPU_NEGATED); + pd->reg = base + desc->offset - PD_H6_PPU_OFFSET; + pd->gate_mask = desc->mask; + + ret = pm_genpd_init(&pd->genpd, NULL, + !sun50i_h6_ppu_power_status(pd)); + if (ret) { + dev_warn(dev, "Failed to add %s power domain: %d\n", + desc->name, ret); + goto out_remove_pds; + } + ppu->domains[i] = &pd->genpd; + } + + ret = of_genpd_add_provider_onecell(dev->of_node, ppu); + if (!ret) + return 0; + + dev_warn(dev, "Failed to add provider: %d\n", ret); +out_remove_pds: + for (i--; i >= 0; i--) + pm_genpd_remove(&pds[i].genpd); + + return ret; +} + +static const struct of_device_id sun50i_h6_ppu_of_match[] = { + { .compatible = "allwinner,sun50i-h6-prcm-ppu", + .data = &sun50i_h6_ppu_data }, + { .compatible = "allwinner,sun50i-h616-prcm-ppu", + .data = &sun50i_h616_ppu_data }, + { } +}; +MODULE_DEVICE_TABLE(of, sun50i_h6_ppu_of_match); + +static struct platform_driver sun50i_h6_ppu_driver = { + .probe = sun50i_h6_ppu_probe, + .driver = { + .name = "sun50i-h6-prcm-ppu", + .of_match_table = sun50i_h6_ppu_of_match, + /* Power domains cannot be removed while they are in use. */ + .suppress_bind_attrs = true, + }, +}; +module_platform_driver(sun50i_h6_ppu_driver); + +MODULE_AUTHOR("Andre Przywara "); +MODULE_DESCRIPTION("Allwinner H6 PRCM power domain driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/pmdomain/sunxi/sun55i-pck600.c b/drivers/pmdomain/sunxi/sun55i-pck600.c new file mode 100644 index 000000000..8a37d11b6 --- /dev/null +++ b/drivers/pmdomain/sunxi/sun55i-pck600.c @@ -0,0 +1,270 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Allwinner PCK-600 power domain support + * + * Copyright (c) 2025 Chen-Yu Tsai + * + * The hardware is likely based on the Arm PCK-600 IP, since some of + * the registers match Arm's documents, with additional delay controls + * that are in registers listed as reserved. + * + * Documents include: + * - "Arm CoreLink PCK-600 Power Control Kit" TRM + * - "Arm Power Policy Unit" architecture specification (DEN0051E) + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PPU_PWPR 0x0 +#define PPU_PWSR 0x8 +#define PPU_DCDR0 0x170 +#define PPU_DCDR1 0x174 + +/* shared definition for PPU_PWPR and PPU_PWSR */ +#define PPU_PWR_STATUS GENMASK(3, 0) +#define PPU_POWER_MODE_ON 0x8 +#define PPU_POWER_MODE_OFF 0x0 + +#define PPU_REG_SIZE 0x1000 + +struct sunxi_pck600_pd_desc { + const char *name; + unsigned int flags; +}; + +struct sunxi_pck600_desc { + const struct sunxi_pck600_pd_desc *pd_descs; + unsigned int num_domains; + u32 logic_power_switch0_delay_offset; + u32 logic_power_switch1_delay_offset; + u32 off2on_delay_offset; + u32 device_ctrl0_delay; + u32 device_ctrl1_delay; + u32 logic_power_switch0_delay; + u32 logic_power_switch1_delay; + u32 off2on_delay; + bool has_rst_clk; +}; + +struct sunxi_pck600_pd { + struct generic_pm_domain genpd; + struct sunxi_pck600 *pck; + void __iomem *base; +}; + +struct sunxi_pck600 { + struct device *dev; + struct genpd_onecell_data genpd_data; + struct sunxi_pck600_pd pds[]; +}; + +#define to_sunxi_pd(gpd) container_of(gpd, struct sunxi_pck600_pd, genpd) + +static int sunxi_pck600_pd_set_power(struct sunxi_pck600_pd *pd, bool on) +{ + struct sunxi_pck600 *pck = pd->pck; + struct generic_pm_domain *genpd = &pd->genpd; + int ret; + u32 val, reg; + + val = on ? PPU_POWER_MODE_ON : PPU_POWER_MODE_OFF; + + reg = readl(pd->base + PPU_PWPR); + FIELD_MODIFY(PPU_PWR_STATUS, ®, val); + writel(reg, pd->base + PPU_PWPR); + + /* push write out to hardware */ + reg = readl(pd->base + PPU_PWPR); + + ret = readl_poll_timeout_atomic(pd->base + PPU_PWSR, reg, + FIELD_GET(PPU_PWR_STATUS, reg) == val, + 0, 10000); + if (ret) + dev_err(pck->dev, "failed to turn domain \"%s\" %s: %d\n", + genpd->name, str_on_off(on), ret); + + return ret; +} + +static int sunxi_pck600_power_on(struct generic_pm_domain *domain) +{ + struct sunxi_pck600_pd *pd = to_sunxi_pd(domain); + + return sunxi_pck600_pd_set_power(pd, true); +} + +static int sunxi_pck600_power_off(struct generic_pm_domain *domain) +{ + struct sunxi_pck600_pd *pd = to_sunxi_pd(domain); + + return sunxi_pck600_pd_set_power(pd, false); +} + +static void sunxi_pck600_pd_setup(struct sunxi_pck600_pd *pd, + const struct sunxi_pck600_desc *desc) +{ + writel(desc->device_ctrl0_delay, pd->base + PPU_DCDR0); + writel(desc->device_ctrl1_delay, pd->base + PPU_DCDR1); + writel(desc->logic_power_switch0_delay, + pd->base + desc->logic_power_switch0_delay_offset); + writel(desc->logic_power_switch1_delay, + pd->base + desc->logic_power_switch1_delay_offset); + writel(desc->off2on_delay, pd->base + desc->off2on_delay_offset); +} + +static int sunxi_pck600_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + const struct sunxi_pck600_desc *desc; + struct genpd_onecell_data *genpds; + struct sunxi_pck600 *pck; + struct reset_control *rst; + struct clk *clk; + void __iomem *base; + int i, ret; + + desc = of_device_get_match_data(dev); + + pck = devm_kzalloc(dev, struct_size(pck, pds, desc->num_domains), GFP_KERNEL); + if (!pck) + return -ENOMEM; + + pck->dev = &pdev->dev; + platform_set_drvdata(pdev, pck); + + genpds = &pck->genpd_data; + genpds->num_domains = desc->num_domains; + genpds->domains = devm_kcalloc(dev, desc->num_domains, + sizeof(*genpds->domains), GFP_KERNEL); + if (!genpds->domains) + return -ENOMEM; + + base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(base)) + return PTR_ERR(base); + + if (desc->has_rst_clk) { + rst = devm_reset_control_get_exclusive_released(dev, NULL); + if (IS_ERR(rst)) + return dev_err_probe(dev, PTR_ERR(rst), "failed to get reset control\n"); + } + + clk = devm_clk_get_enabled(dev, NULL); + if (IS_ERR(clk)) + return dev_err_probe(dev, PTR_ERR(clk), "failed to get clock\n"); + + for (i = 0; i < desc->num_domains; i++) { + struct sunxi_pck600_pd *pd = &pck->pds[i]; + const struct sunxi_pck600_pd_desc *pd_desc = &desc->pd_descs[i]; + + pd->genpd.name = pd_desc->name; + pd->genpd.power_off = sunxi_pck600_power_off; + pd->genpd.power_on = sunxi_pck600_power_on; + pd->genpd.flags = pd_desc->flags; + pd->base = base + PPU_REG_SIZE * i; + + sunxi_pck600_pd_setup(pd, desc); + ret = pm_genpd_init(&pd->genpd, NULL, false); + if (ret) { + dev_err_probe(dev, ret, "failed to initialize power domain\n"); + goto err_remove_pds; + } + + genpds->domains[i] = &pd->genpd; + } + + ret = of_genpd_add_provider_onecell(dev_of_node(dev), genpds); + if (ret) { + dev_err_probe(dev, ret, "failed to add PD provider\n"); + goto err_remove_pds; + } + + return 0; + +err_remove_pds: + for (i--; i >= 0; i--) + pm_genpd_remove(genpds->domains[i]); + + return ret; +} + +static const struct sunxi_pck600_pd_desc sun55i_a523_pck600_pds[] = { + { "VE", }, { "GPU", }, { "VI", }, { "VO0", }, { "VO1", }, + { "DE", }, { "NAND", }, { "PCIE", }, +}; + +static const struct sunxi_pck600_desc sun55i_a523_pck600_desc = { + .pd_descs = sun55i_a523_pck600_pds, + .num_domains = ARRAY_SIZE(sun55i_a523_pck600_pds), + .logic_power_switch0_delay_offset = 0xc00, + .logic_power_switch1_delay_offset = 0xc04, + .off2on_delay_offset = 0xc10, + .device_ctrl0_delay = 0xffffff, + .device_ctrl1_delay = 0xffff, + .logic_power_switch0_delay = 0x8080808, + .logic_power_switch1_delay = 0x808, + .off2on_delay = 0x8, + .has_rst_clk = true, +}; + +static const struct sunxi_pck600_pd_desc sun60i_a733_pck600_pds[] = { + { "VI", }, { "DE_SYS", }, { "VE_DEC", }, { "VE_ENC", }, { "NPU", }, + { "GPU_TOP", }, { "GPU_CORE", GENPD_FLAG_ALWAYS_ON }, + { "PCIE", }, { "USB2", }, { "VO", }, { "VO1", }, +}; + +static const struct sunxi_pck600_desc sun60i_a733_pck600_desc = { + .pd_descs = sun60i_a733_pck600_pds, + .num_domains = ARRAY_SIZE(sun60i_a733_pck600_pds), + .logic_power_switch0_delay_offset = 0xc00, + .logic_power_switch1_delay_offset = 0xc04, + .off2on_delay_offset = 0xc10, + .device_ctrl0_delay = 0x1f1f1f, + .device_ctrl1_delay = 0x1f1f, + .logic_power_switch0_delay = 0x8080808, + .logic_power_switch1_delay = 0x808, + .off2on_delay = 0x8, + .has_rst_clk = false, +}; + +static const struct of_device_id sunxi_pck600_of_match[] = { + { + .compatible = "allwinner,sun55i-a523-pck-600", + .data = &sun55i_a523_pck600_desc, + }, + { + .compatible = "allwinner,sun60i-a733-pck-600", + .data = &sun60i_a733_pck600_desc, + }, + {} +}; +MODULE_DEVICE_TABLE(of, sunxi_pck600_of_match); + +static struct platform_driver sunxi_pck600_driver = { + .probe = sunxi_pck600_probe, + .driver = { + .name = "sunxi-pck-600", + .of_match_table = sunxi_pck600_of_match, + /* Power domains cannot be removed if in use. */ + .suppress_bind_attrs = true, + }, +}; +module_platform_driver(sunxi_pck600_driver); + +MODULE_DESCRIPTION("Allwinner PCK-600 power domain driver"); +MODULE_AUTHOR("Chen-Yu Tsai "); +MODULE_LICENSE("GPL"); -- cgit v1.3.1