From b22262deebf3e57cf616baedebafd95072cbf02f Mon Sep 17 00:00:00 2001 From: gengzonglin Date: Wed, 17 Jul 2024 17:15:31 +0800 Subject: [PATCH 105/222] feat(pmdomain): Add power domain framework support. Changelogs: 1.Add PMU drive. 2.Temporarily force power on all power domains in DTS. Signed-off-by: gengzonglin --- .../dts/eswin/eswin-win2030-die0-soc.dtsi | 23 +- drivers/pmdomain/Makefile | 1 + drivers/pmdomain/eswin/Makefile | 6 + drivers/pmdomain/eswin/eic770x-pmu.c | 406 ++++++++++++++++++ include/dt-bindings/power/eswin,eic770x-pmu.h | 33 ++ 5 files changed, 462 insertions(+), 7 deletions(-) create mode 100644 drivers/pmdomain/eswin/Makefile create mode 100644 drivers/pmdomain/eswin/eic770x-pmu.c create mode 100644 include/dt-bindings/power/eswin,eic770x-pmu.h diff --git a/arch/riscv/boot/dts/eswin/eswin-win2030-die0-soc.dtsi b/arch/riscv/boot/dts/eswin/eswin-win2030-die0-soc.dtsi index 79a64e2abaf3..fea1f577fddc 100644 --- a/arch/riscv/boot/dts/eswin/eswin-win2030-die0-soc.dtsi +++ b/arch/riscv/boot/dts/eswin/eswin-win2030-die0-soc.dtsi @@ -30,6 +30,7 @@ #include #include #include +#include / { compatible = "sifive,hifive-unmatched-a00", "sifive,fu740-c000","sifive,fu740"; @@ -294,12 +295,14 @@ d0_pmu: power-controller@51808000 { #address-cells = <1>; #size-cells = <0>; compatible = "eswin,win2030-pmu-controller"; + #power-domain-cells = <1>; reg = <0x0 0x51808000 0x0 0x8000>; numa-node-id = <0>; d0_pmu_pcie: win2030-pmu-controller-port@0 { compatible = "eswin,win2030-pmu-controller-port"; + id = ; reg_base = <0x0>; - power_status = <1>; + power_status = <2>; power_delay = <6 6 3 3>; clock_delay = <4 2 2 2>; reset_delay = <2 4 2 2>; @@ -309,8 +312,9 @@ d0_pmu_pcie: win2030-pmu-controller-port@0 { }; d0_pmu_dsp1: win2030-pmu-controller-port@40 { compatible = "eswin,win2030-pmu-controller-port"; + id = ; reg_base = <0x40>; - power_status = <1>; + power_status = <2>; power_delay = <6 6 3 3>; clock_delay = <4 2 2 2>; reset_delay = <2 4 2 2>; @@ -320,8 +324,9 @@ d0_pmu_dsp1: win2030-pmu-controller-port@40 { }; d0_pmu_vi: win2030-pmu-controller-port@80 { compatible = "eswin,win2030-pmu-controller-port"; + id = ; reg_base = <0x80>; - power_status = <1>; + power_status = <2>; power_delay = <6 6 3 3>; clock_delay = <4 2 2 2>; reset_delay = <2 4 2 2>; @@ -331,8 +336,9 @@ d0_pmu_vi: win2030-pmu-controller-port@80 { }; d0_pmu_vo: win2030-pmu-controller-port@c0 { compatible = "eswin,win2030-pmu-controller-port"; + id = ; reg_base = <0xc0>; - power_status = <1>; + power_status = <2>; power_delay = <6 6 3 3>; clock_delay = <4 2 2 2>; reset_delay = <2 4 2 2>; @@ -341,8 +347,9 @@ d0_pmu_vo: win2030-pmu-controller-port@c0 { }; d0_pmu_codec: win2030-pmu-controller-port@140 { compatible = "eswin,win2030-pmu-controller-port"; + id = ; reg_base = <0x140>; - power_status = <1>; + power_status = <2>; power_delay = <6 6 3 3>; clock_delay = <4 2 2 2>; reset_delay = <2 4 2 2>; @@ -352,8 +359,9 @@ d0_pmu_codec: win2030-pmu-controller-port@140 { }; d0_pmu_dsp2: win2030-pmu-controller-port@200 { compatible = "eswin,win2030-pmu-controller-port"; + id = ; reg_base = <0x200>; - power_status = <1>; + power_status = <2>; power_delay = <6 6 3 3>; clock_delay = <4 2 2 2>; reset_delay = <2 4 2 2>; @@ -363,8 +371,9 @@ d0_pmu_dsp2: win2030-pmu-controller-port@200 { }; d0_pmu_dsp3: win2030-pmu-controller-port@240 { compatible = "eswin,win2030-pmu-controller-port"; + id = ; reg_base = <0x240>; - power_status = <1>; + power_status = <2>; power_delay = <6 6 3 3>; clock_delay = <4 2 2 2>; reset_delay = <2 4 2 2>; diff --git a/drivers/pmdomain/Makefile b/drivers/pmdomain/Makefile index 666753676e5c..49aed0580d25 100644 --- a/drivers/pmdomain/Makefile +++ b/drivers/pmdomain/Makefile @@ -15,3 +15,4 @@ obj-y += sunxi/ obj-y += tegra/ obj-y += ti/ obj-y += xilinx/ +obj-y += eswin/ diff --git a/drivers/pmdomain/eswin/Makefile b/drivers/pmdomain/eswin/Makefile new file mode 100644 index 000000000000..dcaed5c2aa79 --- /dev/null +++ b/drivers/pmdomain/eswin/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for ESWIN EIC77XX pmu driver +# +# obj-$(CONFIG_EIC77XX_PMU) += eic77xx-pmu.o +obj-y += eic770x-pmu.o diff --git a/drivers/pmdomain/eswin/eic770x-pmu.c b/drivers/pmdomain/eswin/eic770x-pmu.c new file mode 100644 index 000000000000..da8a09d21d8b --- /dev/null +++ b/drivers/pmdomain/eswin/eic770x-pmu.c @@ -0,0 +1,406 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ESWIN PMU Driver + * + * Copyright 2024, Beijing ESWIN Computing Technology Co., Ltd.. All rights reserved. + * SPDX-License-Identifier: GPL-2.0 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Authors: gengzonglin + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PD_CTRL 0x0 // override +#define PD_SW_COLLAPSE 0x4 // sw collapse en +#define PD_SW_PWR 0x8 // power switch ack & power switch en +#define PD_SW_RESET 0xC // reg reset +#define PD_SW_ISO 0x10 // sw clamp io +#define PD_SW_CLK_DISABLE 0x14 // sw clk disable +#define PD_PWR_DLY 0x18 // sw clk disable +#define PD_CLK_DLY 0x1c // sw clk disable +#define PD_RST_DLY 0x20 // sw clk disable +#define PD_CLAMP_DLY 0x24 // sw clk disable +#define PD_DEBUG 0x28 // status + +#define PD_STATUS_MASK 0x03 // STATE 3'b011 power on, 3'b000 power off + +#define eic770x_PMU_TIMEOUT_US 500 + +struct pmu_device_power_delay { + unsigned int dly_en_up; /* wait time after power-on */ + unsigned int dly_en_down; /* delay time of the req signal */ + unsigned int en_up_div_exp; /* */ + unsigned int en_down_div_exp; /* */ +}; + +struct pmu_device_clock_delay { + unsigned int disable_div_exp; + unsigned int nodisable_div_exp; + unsigned int dly_disable_clk; + unsigned int dly_nodisble_clk; +}; + +struct pmu_device_reset_delay { + unsigned int dly_deassert_ares; + unsigned int dly_assert_ares; + unsigned int deassert_div_exp; + unsigned int assert_div_exp; +}; + +struct pmu_device_clamp_delay { + unsigned int dly_unclamp_io; + unsigned int dly_clamp_io; + unsigned int unclamp_div_exp; + unsigned int clamp_div_exp; +}; + +struct eic770x_domain_info +{ + const char *name; + void __iomem *reg_base; + struct device_node *of_node; + unsigned int status; + struct pmu_device_power_delay power_dly; + struct pmu_device_clock_delay clk_dly; + struct pmu_device_reset_delay reset_dly; + struct pmu_device_clamp_delay clamp_dly; +}; + +struct eic770x_pmu { + struct device *dev; + void __iomem *base; + struct eic770x_domain_info *domain_info; + unsigned int num_domains; + struct generic_pm_domain **genpd; + struct genpd_onecell_data genpd_data; +}; + +struct eic770x_pmu_dev { + struct eic770x_domain_info *domain_info; + struct eic770x_pmu *pmu; + struct generic_pm_domain genpd; +}; + +static int eic770x_pmu_get_domain_state(struct eic770x_pmu_dev *pmd, bool *is_on) +{ + *is_on = false; + + if(PD_STATUS_MASK & ioread32(pmd->domain_info->reg_base + PD_DEBUG)) { + *is_on = true; + } + + return 0; +} + +static int eic770x_pmu_set_domain_state(struct eic770x_pmu_dev *pmd, bool off) +{ + iowrite32(0x0, pmd->domain_info->reg_base + PD_CTRL); // power domain cntr use hardware + iowrite32(off, pmd->domain_info->reg_base + PD_SW_COLLAPSE); // collapse 1:power off 0:power on + return 0; +} + +static int eic770x_pmu_domain_on(struct generic_pm_domain *genpd) +{ + struct eic770x_pmu_dev *pmd = container_of(genpd, + struct eic770x_pmu_dev, genpd); + struct eic770x_pmu *pmu = pmd->pmu; + struct device_node *node = pmd->domain_info->of_node; + bool is_on; + int ret; + u32 val; + + eic770x_pmu_get_domain_state(pmd, &is_on); + if (is_on == true) { + dev_info(pmu->dev, "pm domain [%s] was already in power on state.\n", + pmd->genpd.name); + return 0; + } + + dev_info(pmu->dev, "The %s enters power-on process.\n", pmd->genpd.name); + + eic770x_pmu_set_domain_state(pmd, false); //true: power off. + ret = readl_poll_timeout_atomic(pmd->domain_info->reg_base + PD_DEBUG, + val, (val & PD_STATUS_MASK), + 1, eic770x_PMU_TIMEOUT_US); + if (ret) { + dev_err(pmu->dev, "%s: failed to power on\n", + pmd->genpd.name); + return -ETIMEDOUT; + } + + if (!of_property_read_u32(node, "tbus", &val)) { + win2030_tbu_power_by_dev_and_node(pmu->dev, node, true); // tbu power on + dev_info(pmu->dev, "%s power on tbu.\n", pmd->genpd.name); + } + + dev_info(pmu->dev, "The %s ends power-on process.\n", pmd->genpd.name); + return 0; +} + +static int eic770x_pmu_domain_off(struct generic_pm_domain *genpd) +{ + struct eic770x_pmu_dev *pmd = container_of(genpd, + struct eic770x_pmu_dev, genpd); + struct eic770x_pmu *pmu = pmd->pmu; + struct device_node *node = pmd->domain_info->of_node; + bool is_on = false; + int ret; + u32 val; + + eic770x_pmu_get_domain_state(pmd, &is_on); + if (is_on == false) { + dev_info(pmu->dev, "pm domain [%s] was already in power off state.\n", + pmd->genpd.name); + return 0; + } + + dev_info(pmu->dev, "The %s enters power off process.\n", pmd->genpd.name); + + if (!of_property_read_u32(node, "tbus", &val)) { + win2030_tbu_power_by_dev_and_node(pmu->dev, node, false); // tbu power off + dev_info(pmu->dev, "%s power off tbu.\n", pmd->genpd.name); + } + + eic770x_pmu_set_domain_state(pmd, true); //true: power off. + ret = readl_poll_timeout_atomic(pmd->domain_info->reg_base + PD_DEBUG, + val, !(val & PD_STATUS_MASK), + 1, eic770x_PMU_TIMEOUT_US); + if (ret) { + dev_err(pmu->dev, "%s: failed to power off\n", + pmd->genpd.name); + return -ETIMEDOUT; + } + dev_info(pmu->dev, "The %s ends power off process.\n", pmd->genpd.name); + return 0; +} + +static int eic770x_pmu_init_domain(struct eic770x_pmu *pmu, int index) +{ + struct eic770x_pmu_dev *pmd; + int ret; + bool is_on = false; + + pmd = devm_kzalloc(pmu->dev, sizeof(*pmd), GFP_KERNEL); + if (!pmd) + return -ENOMEM; + + pmd->domain_info = &pmu->domain_info[index]; + pmd->pmu = pmu; + + pmd->genpd.name = pmd->domain_info->name; + + if(pmd->domain_info->status == 2) { + pmd->genpd.flags = GENPD_FLAG_ALWAYS_ON; + } + + if(pmd->domain_info->status == 0) { + eic770x_pmu_domain_off(&pmd->genpd); + } + else { + eic770x_pmu_domain_on(&pmd->genpd); + } + + ret = eic770x_pmu_get_domain_state(pmd, &is_on); + if (ret) + dev_warn(pmu->dev, "unable to get current state for %s\n", + pmd->genpd.name); + + pmd->genpd.power_on = eic770x_pmu_domain_on; + pmd->genpd.power_off = eic770x_pmu_domain_off; + pm_genpd_init(&pmd->genpd, NULL, !is_on); + + pmu->genpd_data.domains[index] = &pmd->genpd; + + return 0; +} + + +static int eic770x_pmu_add_domain(struct device *dev, struct eic770x_pmu *pmu) +{ + struct eic770x_domain_info *pd_info; + struct device_node *node; + int id, nval, num_domains; + int ret; + unsigned int val[32]; + + num_domains = device_get_child_node_count(dev); + if (num_domains == 0) + return -ENODEV; + + pmu->domain_info = devm_kcalloc(dev, num_domains, + sizeof(*pd_info), + GFP_KERNEL); + if (!pmu->domain_info) + return -ENOMEM; + + num_domains = 0; + for_each_child_of_node(dev->of_node, node) + { + ret = of_property_read_u32(node, "id", &id); + if (ret) { + dev_err(dev, "Failed to parse pmu id.\n"); + continue; + } + if(id > EIC770X_PD_DSP3) { + dev_err(dev, "pmu id %d out of range.\n", id); + continue; + } + + pd_info = &pmu->domain_info[id]; + pd_info->of_node = node; + unsigned int val_u32; + of_property_read_u32(node, "reg_base", &val_u32); + pd_info->reg_base = pmu->base + val_u32; + + of_property_read_string(node, "label", &pd_info->name); + of_property_read_u32(node, "power_status", &pd_info->status); + + nval = of_property_read_u32_array(node, "power_delay", &val[0], 4); + if(!nval ){ + pd_info->power_dly.dly_en_up = val[0]; + pd_info->power_dly.dly_en_down = val[1]; + pd_info->power_dly.en_up_div_exp = val[2]; + pd_info->power_dly.en_down_div_exp = val[3]; + val_u32 = (pd_info->power_dly.dly_en_up & 0xff) | + ((pd_info->power_dly.dly_en_down & 0xff) << 8) | + ((pd_info->power_dly.en_up_div_exp & 0xf) << 16) | + ((pd_info->power_dly.en_down_div_exp & 0xf) << 20); + iowrite32( val_u32, pd_info->reg_base + PD_PWR_DLY); + } + + nval = of_property_read_u32_array(node, "clock_delay", &val[0], 4); + if(!nval){ + pd_info->clk_dly.dly_nodisble_clk = val[0]; + pd_info->clk_dly.dly_disable_clk = val[1]; + pd_info->clk_dly.nodisable_div_exp = val[2]; + pd_info->clk_dly.disable_div_exp = val[3]; + val_u32 = (pd_info->clk_dly.dly_nodisble_clk & 0xff) | + ((pd_info->clk_dly.dly_disable_clk & 0xff) << 8) | + ((pd_info->clk_dly.nodisable_div_exp & 0xf) << 16) | + ((pd_info->clk_dly.disable_div_exp & 0xf) << 20); + iowrite32( val_u32, pd_info->reg_base + PD_CLK_DLY); + } + + nval = of_property_read_u32_array(node, "reset_delay", &val[0], 4); + if(!nval){ + pd_info->reset_dly.dly_assert_ares = val[0]; + pd_info->reset_dly.dly_deassert_ares = val[1]; + pd_info->reset_dly.assert_div_exp = val[2]; + pd_info->reset_dly.deassert_div_exp = val[3]; + val_u32 = (pd_info->reset_dly.dly_assert_ares & 0xff) | + ((pd_info->reset_dly.dly_deassert_ares & 0xff) << 8) | + ((pd_info->reset_dly.assert_div_exp & 0xf) << 16) | + ((pd_info->reset_dly.deassert_div_exp & 0xf) << 20); + iowrite32( val_u32, pd_info->reg_base + PD_RST_DLY); + } + + nval = of_property_read_u32_array(node, "clamp_delay", &val[0], 4); + if(!nval){ + pd_info->clamp_dly.dly_unclamp_io = val[0]; + pd_info->clamp_dly.dly_clamp_io = val[1]; + pd_info->clamp_dly.unclamp_div_exp = val[2]; + pd_info->clamp_dly.clamp_div_exp = val[3]; + val_u32 = (pd_info->clamp_dly.dly_unclamp_io & 0xff) | + ((pd_info->clamp_dly.dly_clamp_io & 0xff) << 8) | + ((pd_info->clamp_dly.unclamp_div_exp & 0xf) << 16) | + ((pd_info->clamp_dly.clamp_div_exp & 0xf) << 20); + iowrite32( val_u32, pd_info->reg_base + PD_CLAMP_DLY); + } + + num_domains++; + } + pmu->num_domains = num_domains; + return 0; +} + +static int eic770x_pmu_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct eic770x_pmu *pmu; + unsigned int i; + int ret; + + dev_info(dev, "start registe power domains\n"); + + pmu = devm_kzalloc(dev, sizeof(*pmu), GFP_KERNEL); + if (!pmu) + return -ENOMEM; + + pmu->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(pmu->base)) + return PTR_ERR(pmu->base); + + eic770x_pmu_add_domain(dev, pmu); + + pmu->genpd = devm_kcalloc(dev, pmu->num_domains, + sizeof(struct generic_pm_domain *), + GFP_KERNEL); + if (!pmu->genpd) + return -ENOMEM; + + pmu->dev = dev; + pmu->genpd_data.domains = pmu->genpd; + pmu->genpd_data.num_domains = pmu->num_domains; + + for (i = 0; i < pmu->num_domains; i++) { + ret = eic770x_pmu_init_domain(pmu, i); + if (ret) { + dev_err(dev, "failed to initialize power domain\n"); + return ret; + } + } + + ret = of_genpd_add_provider_onecell(np, &pmu->genpd_data); + if (ret) { + dev_err(dev, "failed to register genpd driver: %d\n", ret); + return ret; + } + + dev_info(dev, "registered %u power domains\n", i); + + return 0; +} + + +static const struct of_device_id eic770x_pmu_of_match[] = { + { + .compatible = "eswin,win2030-pmu-controller", + }, { + /* sentinel */ + } +}; + +static struct platform_driver eic770x_pmu_driver = { + .probe = eic770x_pmu_probe, + .driver = { + .name = "eswin-pmu", + .of_match_table = eic770x_pmu_of_match, + }, +}; +builtin_platform_driver(eic770x_pmu_driver); + +MODULE_AUTHOR("Geng Zonglin "); +MODULE_DESCRIPTION("ESWIN eic770x PMU Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/include/dt-bindings/power/eswin,eic770x-pmu.h b/include/dt-bindings/power/eswin,eic770x-pmu.h new file mode 100644 index 000000000000..aca28e5543fe --- /dev/null +++ b/include/dt-bindings/power/eswin,eic770x-pmu.h @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ESWIN PMU Driver + * + * Copyright 2024, Beijing ESWIN Computing Technology Co., Ltd.. All rights reserved. + * SPDX-License-Identifier: GPL-2.0 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Authors: gengzonglin + */ + +#ifndef __DT_BINDINGS_EIC770X_POWER_H__ +#define __DT_BINDINGS_EIC770X_POWER_H__ + +#define EIC770X_PD_PCIE 0 +#define EIC770X_PD_DSP1 1 +#define EIC770X_PD_VI 2 +#define EIC770X_PD_VO 3 +#define EIC770X_PD_CODEC 4 +#define EIC770X_PD_DSP2 5 +#define EIC770X_PD_DSP3 6 +#endif -- 2.47.0