From patchwork Fri Dec 10 17:01:10 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Flora Fu X-Patchwork-Id: 12670251 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from bombadil.infradead.org (bombadil.infradead.org [198.137.202.133]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id B6814C433EF for ; Fri, 10 Dec 2021 17:12:56 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.infradead.org; s=bombadil.20210309; h=Sender: Content-Transfer-Encoding:Content-Type:List-Subscribe:List-Help:List-Post: List-Archive:List-Unsubscribe:List-Id:MIME-Version:References:In-Reply-To: Message-ID:Date:Subject:CC:To:From:Reply-To:Content-ID:Content-Description: Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID: List-Owner; bh=3Z6HxcXxQoxs78I51xU40zpYbxlzHRbt8R3RB4ZlpPE=; b=dkOl4JQ74IFN0y xynX/3OdW+VK3hu9Zjtjs6LTkZPririmtb6c0QHk62RTIYkw4pgklZB1uk8rROKfNic2Ym4Ud7El2 rPtfd2ur6xwVl+Lt4IfXEsMa4LJo/G843bRIPjz/9r3bDrmkWfd333AqyQIO4epzW1aArfIs6xw5E Mz05Lu40IvDjNvGsFANws+I69xma2fANuV8Wlpz8/lWg2TSOWuemozDl+nDojEkQb0yKSwadrofN3 KuAA0stxs/x3rMXUv9fFQIWVVw8q/dTdo7Lq+kkItseqn2Ka3QpcqoWjFb7RYaJCO22ag+XHuQ6Mv DD/gA5Je+uXviSYrtRVQ==; Received: from localhost ([::1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.94.2 #2 (Red Hat Linux)) id 1mvjRw-002kqg-KL; Fri, 10 Dec 2021 17:12:48 +0000 Received: from mailgw01.mediatek.com ([216.200.240.184]) by bombadil.infradead.org with esmtps (Exim 4.94.2 #2 (Red Hat Linux)) id 1mvjR5-002kW7-4V; Fri, 10 Dec 2021 17:11:58 +0000 X-UUID: 4a751f92b7534fd2909fefb46f077ecd-20211210 X-UUID: 4a751f92b7534fd2909fefb46f077ecd-20211210 Received: from mtkcas66.mediatek.inc [(172.29.193.44)] by mailgw01.mediatek.com (envelope-from ) (musrelay.mediatek.com ESMTP with TLSv1.2 ECDHE-RSA-AES256-SHA384 256/256) with ESMTP id 402397510; Fri, 10 Dec 2021 10:11:48 -0700 Received: from mtkexhb01.mediatek.inc (172.21.101.102) by MTKMBS62DR.mediatek.inc (172.29.94.18) with Microsoft SMTP Server (TLS) id 15.0.1497.2; Fri, 10 Dec 2021 09:01:51 -0800 Received: from mtkcas11.mediatek.inc (172.21.101.40) by mtkexhb01.mediatek.inc (172.21.101.102) with Microsoft SMTP Server (TLS) id 15.0.1497.2; Sat, 11 Dec 2021 01:01:42 +0800 Received: from mtksdccf07.mediatek.inc (172.21.84.99) by mtkcas11.mediatek.inc (172.21.101.73) with Microsoft SMTP Server id 15.0.1497.2 via Frontend Transport; Sat, 11 Dec 2021 01:01:42 +0800 From: Flora Fu To: Matthias Brugger , Liam Girdwood , Mark Brown CC: , , , Flora Fu , "Yong Wu" , JB Tsai , Pi-Cheng Chen , Chun-Jie Chen Subject: [PATCH v4 5/8] soc: mediatek: apu: Add apusys and add apu power domain driver Date: Sat, 11 Dec 2021 01:01:10 +0800 Message-ID: <20211210170113.30063-6-flora.fu@mediatek.com> X-Mailer: git-send-email 2.18.0 In-Reply-To: <20211210170113.30063-1-flora.fu@mediatek.com> References: <20211210170113.30063-1-flora.fu@mediatek.com> MIME-Version: 1.0 X-MTK: N X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20211210_091155_233567_EEC4F2F1 X-CRM114-Status: GOOD ( 23.60 ) X-BeenThere: linux-mediatek@lists.infradead.org X-Mailman-Version: 2.1.34 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: "Linux-mediatek" Errors-To: linux-mediatek-bounces+linux-mediatek=archiver.kernel.org@lists.infradead.org Add the apusys in soc. Add driver for apu power domains. APU power domain shall be enabled before accessing the internal sub modules. Signed-off-by: Flora Fu --- drivers/soc/mediatek/Kconfig | 12 + drivers/soc/mediatek/Makefile | 1 + drivers/soc/mediatek/apusys/Kconfig | 14 + drivers/soc/mediatek/apusys/Makefile | 2 + drivers/soc/mediatek/apusys/mtk-apu-pm.c | 727 +++++++++++++++++++++++ 5 files changed, 756 insertions(+) create mode 100644 drivers/soc/mediatek/apusys/Kconfig create mode 100644 drivers/soc/mediatek/apusys/Makefile create mode 100644 drivers/soc/mediatek/apusys/mtk-apu-pm.c diff --git a/drivers/soc/mediatek/Kconfig b/drivers/soc/mediatek/Kconfig index fdd8bc08569e..47f003ba2e28 100644 --- a/drivers/soc/mediatek/Kconfig +++ b/drivers/soc/mediatek/Kconfig @@ -5,6 +5,18 @@ menu "MediaTek SoC drivers" depends on ARCH_MEDIATEK || COMPILE_TEST +config MTK_APUSYS + bool "MediaTek APUSYS Support" + select REGMAP + help + Say yes here to add support for the MediaTek AI Processing Unit + Subsystem (APUSYS). + The APUSYS is a proprietary hardware in SoC to support AI + operations. The drivers can be enabled individually within this menu. + If unsure, say N. + +source "drivers/soc/mediatek/apusys/Kconfig" + config MTK_CMDQ tristate "MediaTek CMDQ Support" depends on ARCH_MEDIATEK || COMPILE_TEST diff --git a/drivers/soc/mediatek/Makefile b/drivers/soc/mediatek/Makefile index 90270f8114ed..e46e7a3c21e7 100644 --- a/drivers/soc/mediatek/Makefile +++ b/drivers/soc/mediatek/Makefile @@ -1,4 +1,5 @@ # SPDX-License-Identifier: GPL-2.0-only +obj-$(CONFIG_MTK_APUSYS) += apusys/ obj-$(CONFIG_MTK_CMDQ) += mtk-cmdq-helper.o obj-$(CONFIG_MTK_DEVAPC) += mtk-devapc.o obj-$(CONFIG_MTK_INFRACFG) += mtk-infracfg.o diff --git a/drivers/soc/mediatek/apusys/Kconfig b/drivers/soc/mediatek/apusys/Kconfig new file mode 100644 index 000000000000..332e323e02ae --- /dev/null +++ b/drivers/soc/mediatek/apusys/Kconfig @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: GPL-2.0-only +if MTK_APUSYS + +config MTK_APU_PM + tristate "MediaTek APU power management support" + select REGMAP + select PM_GENERIC_DOMAINS if PM + help + Say yes here to add support for power management control + to MediaTek AI Processing Unit Subsystem (APUSYS). + APU power domain shall be enabled before accessing the + internal sub modules. + +endif # MTK_APUSYS diff --git a/drivers/soc/mediatek/apusys/Makefile b/drivers/soc/mediatek/apusys/Makefile new file mode 100644 index 000000000000..8821c0f0b7b7 --- /dev/null +++ b/drivers/soc/mediatek/apusys/Makefile @@ -0,0 +1,2 @@ +# SPDX-License-Identifier: GPL-2.0 +obj-$(CONFIG_MTK_APU_PM) += mtk-apu-pm.o diff --git a/drivers/soc/mediatek/apusys/mtk-apu-pm.c b/drivers/soc/mediatek/apusys/mtk-apu-pm.c new file mode 100644 index 000000000000..10dd30052c46 --- /dev/null +++ b/drivers/soc/mediatek/apusys/mtk-apu-pm.c @@ -0,0 +1,727 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2021 MediaTek Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define APU_PD_IPUIF_HW_CG BIT(0) +#define APU_PD_RPC_AUTO_BUCK BIT(1) +#define APU_PD_CAPS(_pd, _x) ((_pd)->data->caps & (_x)) + +#define MTK_POLL_DELAY_US 10 +#define MTK_POLL_TIMEOUT USEC_PER_SEC + +/* rpc_top_con*/ +#define SLEEP_REQ BIT(0) +#define APU_BUCK_ELS_EN BIT(3) + +/*conn_clr, conn1_clr, vcore_clr */ +#define CG_CLR (0xFFFFFFFF) + +/* mt8192 rpc_sw_type (0..7) */ +#define MT8192_RPC_SW_TYPE(x) (0x200 + ((x) * 0x10)) + +/* rpc_sw_type*/ +static const struct reg_sequence mt8192_rpc_sw_type[] = { + { MT8192_RPC_SW_TYPE(0), 0xFF }, + { MT8192_RPC_SW_TYPE(2), 0x7 }, + { MT8192_RPC_SW_TYPE(3), 0x7 }, + { MT8192_RPC_SW_TYPE(6), 0x3 }, +}; + +struct apu_top_domain { + u32 spm_ext_buck_iso; + u32 spm_ext_buck_iso_mask; + u32 spm_cross_wake_m01; + u32 wake_apu; + u32 spm_other_pwr; + u32 pwr_status; + u32 conn_clr; + u32 conn1_clr; + u32 vcore_clr; + u32 rpc_top_con; + u32 rpc_top_con_init_mask; + u32 rpc_top_sel; + u32 rpc_top_intf_pwr_rdy; + u32 pwr_rdy; + const struct reg_sequence *rpc_sw_type; + int num_rpc_sw; +}; + +static struct apu_top_domain mt8192_top_reg = { + .spm_ext_buck_iso = 0x39C, + .spm_ext_buck_iso_mask = 0x21, + .spm_cross_wake_m01 = 0x670, + .wake_apu = BIT(0), + .spm_other_pwr = 0x178, + .pwr_status = BIT(5), + .conn_clr = 0x8, + .vcore_clr = 0x8, + .rpc_top_con = 0x0, + .rpc_top_con_init_mask = 0x49E, + .rpc_top_sel = 0x4, + .rpc_top_intf_pwr_rdy = 0x44, + .pwr_rdy = BIT(0), + .rpc_sw_type = mt8192_rpc_sw_type, + .num_rpc_sw = ARRAY_SIZE(mt8192_rpc_sw_type), +}; + +struct apusys { + struct device *dev; + struct regmap *scpsys; + struct regmap *conn; + struct regmap *conn1; + struct regmap *vcore; + struct regmap *rpc; + struct regulator *vsram_supply; + const struct apu_pm_data *data; + struct genpd_onecell_data pd_data; + struct generic_pm_domain *domains[]; +}; + +struct apu_domain { + struct generic_pm_domain genpd; + const struct apu_domain_data *data; + struct apusys *apusys; + struct regulator *domain_supply; + int num_clks; + struct clk_bulk_data *clks; + struct clk *clk_top_conn; + struct clk *clk_top_ipu_if; + struct clk *clk_off; + struct clk *clk_on_def; +}; + +struct apu_domain_data { + int domain_idx; + char *name; + struct apu_top_domain *topd; + u8 caps; +}; + +struct apu_pm_data { + const struct apu_domain_data *domains_data; + int num_domains; +}; + +#define to_apu_domain(gpd) container_of(gpd, struct apu_domain, genpd) + +static int apu_top_init_hw(struct apu_domain *pd) +{ + struct apusys *apusys = pd->apusys; + int ret; + + if (APU_PD_CAPS(pd, APU_PD_IPUIF_HW_CG)) { + ret = clk_prepare_enable(pd->clk_top_conn); + if (ret) { + dev_err(apusys->dev, "Failed enable top_conn clk\n"); + return ret; + } + + ret = clk_set_parent(pd->clk_top_ipu_if, pd->clk_on_def); + if (ret) { + dev_err(apusys->dev, "Failed set ipu_if mux\n"); + goto err_clk; + } + + /* The ipu_if clock is gating by HW. Only enable once. */ + if (!__clk_is_enabled(pd->clk_top_ipu_if)) { + ret = clk_prepare_enable(pd->clk_top_ipu_if); + if (ret) { + dev_err(apusys->dev, "Failed enable ipu_if\n"); + goto err_clk; + } + } + } else { + ret = clk_bulk_prepare_enable(pd->num_clks, pd->clks); + if (ret) + goto err_clk; + } + + /* + * set memory type to PD or sleep group + * sw_type register for each memory group, set to PD mode default + */ + if (pd->data->topd->num_rpc_sw) { + ret = regmap_register_patch(apusys->rpc, + pd->data->topd->rpc_sw_type, + pd->data->topd->num_rpc_sw); + if (ret) { + dev_err(apusys->dev, "Failed to rpc patch: %d\n", ret); + goto err_clk; + } + } + + /* mask RPC IRQ and bypass WFI */ + ret = regmap_set_bits(apusys->rpc, pd->data->topd->rpc_top_sel, + pd->data->topd->rpc_top_con_init_mask); + if (ret) + goto err_clk; + + if (APU_PD_CAPS(pd, APU_PD_RPC_AUTO_BUCK)) { + ret = regmap_set_bits(apusys->rpc, + pd->data->topd->rpc_top_con, + APU_BUCK_ELS_EN); + if (ret) + goto err_clk; + } + + if (APU_PD_CAPS(pd, APU_PD_IPUIF_HW_CG)) { + clk_disable_unprepare(pd->clk_top_conn); + ret = clk_set_parent(pd->clk_top_ipu_if, pd->clk_off); + if (ret) { + dev_err(apusys->dev, "Failed set ipu_if rate\n"); + goto err_clk; + } + } else { + clk_bulk_disable_unprepare(pd->num_clks, pd->clks); + } + + return 0; + +err_clk: + if (APU_PD_CAPS(pd, APU_PD_IPUIF_HW_CG)) { + clk_disable_unprepare(pd->clk_top_conn); + clk_disable_unprepare(pd->clk_top_ipu_if); + } else { + clk_bulk_disable_unprepare(pd->num_clks, pd->clks); + } + + return ret; +} + +static const struct apu_domain_data apu_domain_data_mt8192[] = { + { + .domain_idx = 0, + .name = "apu-top", + .caps = APU_PD_IPUIF_HW_CG, + .topd = &mt8192_top_reg, + } +}; + +static const struct apu_pm_data mt8192_apu_pm_data = { + .domains_data = apu_domain_data_mt8192, + .num_domains = ARRAY_SIZE(apu_domain_data_mt8192), +}; + +static int apu_top_power_on(struct generic_pm_domain *genpd) +{ + struct apu_domain *pd = to_apu_domain(genpd); + struct apusys *apusys = pd->apusys; + int ret, tmp; + + if (apusys->vsram_supply) { + ret = regulator_enable(apusys->vsram_supply); + if (ret) + return ret; + } + + if (pd->domain_supply) { + ret = regulator_enable(pd->domain_supply); + if (ret) + goto err_regulator; + } + + ret = regmap_clear_bits(apusys->scpsys, + pd->data->topd->spm_ext_buck_iso, + pd->data->topd->spm_ext_buck_iso_mask); + if (ret) + goto err_regulator; + + ret = regmap_set_bits(apusys->rpc, pd->data->topd->rpc_top_sel, + pd->data->topd->rpc_top_con_init_mask); + if (ret) + goto err_regulator; + + if (APU_PD_CAPS(pd, APU_PD_IPUIF_HW_CG)) { + ret = clk_prepare_enable(pd->clk_top_conn); + if (ret) { + dev_err(apusys->dev, "Failed enable top_conn clk\n"); + goto err_clk; + } + + ret = clk_set_parent(pd->clk_top_ipu_if, pd->clk_on_def); + if (ret) { + dev_err(apusys->dev, "Failed set ipu_if mux\n"); + goto err_clk; + } + + if (!__clk_is_enabled(pd->clk_top_ipu_if)) { + ret = clk_prepare_enable(pd->clk_top_ipu_if); + if (ret) { + dev_err(apusys->dev, "Failed enable ipu_if\n"); + goto err_clk; + } + } + } else { + ret = clk_bulk_prepare_enable(pd->num_clks, pd->clks); + if (ret) + goto err_clk; + } + + ret = regmap_set_bits(apusys->scpsys, + pd->data->topd->spm_cross_wake_m01, + pd->data->topd->wake_apu); + if (ret) + goto err_clk; + + ret = regmap_read_poll_timeout(apusys->scpsys, + pd->data->topd->spm_other_pwr, + tmp, + (tmp & pd->data->topd->pwr_status) == + pd->data->topd->pwr_status, + MTK_POLL_DELAY_US, MTK_POLL_TIMEOUT); + if (ret) { + dev_err(apusys->dev, "apu top on wait SPM PWR_ACK != 0\n"); + goto err_clk; + } + + ret = regmap_read_poll_timeout(apusys->rpc, + pd->data->topd->rpc_top_intf_pwr_rdy, + tmp, (tmp & pd->data->topd->pwr_rdy) == + pd->data->topd->pwr_rdy, + MTK_POLL_DELAY_US, MTK_POLL_TIMEOUT); + if (ret) { + dev_err(apusys->dev, "apu top on wait RPC PWR_RDY != 0\n"); + goto err_clk; + } + + if (apusys->vcore) { + ret = regmap_write(apusys->vcore, + pd->data->topd->vcore_clr, CG_CLR); + if (ret) + goto err_clk; + } + + if (apusys->conn) { + ret = regmap_write(apusys->conn, + pd->data->topd->conn_clr, CG_CLR); + if (ret) + goto err_clk; + } + + if (apusys->conn1) { + ret = regmap_write(apusys->conn1, + pd->data->topd->conn1_clr, CG_CLR); + if (ret) + goto err_clk; + } + + return 0; + +err_clk: + if (APU_PD_CAPS(pd, APU_PD_IPUIF_HW_CG)) { + clk_disable_unprepare(pd->clk_top_conn); + clk_disable_unprepare(pd->clk_top_ipu_if); + } else { + clk_bulk_disable_unprepare(pd->num_clks, pd->clks); + } + if (pd->domain_supply) + ret = regulator_disable(pd->domain_supply); +err_regulator: + if (apusys->vsram_supply) + ret = regulator_disable(apusys->vsram_supply); + + return ret; +} + +static int apu_top_power_off(struct generic_pm_domain *genpd) +{ + struct apu_domain *pd = to_apu_domain(genpd); + struct apusys *apusys = pd->apusys; + int ret, tmp; + + if (apusys->vcore) { + ret = regmap_write(apusys->vcore, + pd->data->topd->vcore_clr, CG_CLR); + if (ret) + return ret; + } + + if (apusys->conn) { + ret = regmap_write(apusys->conn, + pd->data->topd->conn_clr, CG_CLR); + if (ret) + return ret; + } + + if (apusys->conn1) { + ret = regmap_write(apusys->conn1, + pd->data->topd->conn1_clr, CG_CLR); + if (ret) + return ret; + } + + ret = regmap_clear_bits(apusys->scpsys, + pd->data->topd->spm_cross_wake_m01, + pd->data->topd->wake_apu); + if (ret) + return ret; + + ret = regmap_set_bits(apusys->rpc, + pd->data->topd->rpc_top_con, SLEEP_REQ); + if (ret) + return ret; + + ret = regmap_read_poll_timeout(apusys->rpc, + pd->data->topd->rpc_top_intf_pwr_rdy, + tmp, + (tmp & pd->data->topd->pwr_rdy) == 0x0, + MTK_POLL_DELAY_US, MTK_POLL_TIMEOUT); + if (ret) { + dev_err(apusys->dev, "apu top off wait RPC PWR_RDY != 0\n"); + return ret; + } + + ret = regmap_read_poll_timeout(apusys->scpsys, + pd->data->topd->spm_other_pwr, tmp, + (tmp & pd->data->topd->pwr_status) == 0x0, + MTK_POLL_DELAY_US, MTK_POLL_TIMEOUT); + if (ret) { + dev_err(apusys->dev, "apu top off wait SPM PWR_ACK != 0\n"); + return ret; + } + + if (APU_PD_CAPS(pd, APU_PD_IPUIF_HW_CG)) { + clk_disable_unprepare(pd->clk_top_conn); + ret = clk_set_parent(pd->clk_top_ipu_if, pd->clk_off); + if (ret) { + dev_err(apusys->dev, "Failed set ipu_if rate\n"); + return ret; + } + } else { + clk_bulk_disable_unprepare(pd->num_clks, pd->clks); + } + + ret = regmap_set_bits(apusys->scpsys, + pd->data->topd->spm_ext_buck_iso, + pd->data->topd->spm_ext_buck_iso_mask); + if (ret) + return ret; + + if (apusys->vsram_supply) { + ret = regulator_disable(apusys->vsram_supply); + if (ret) + return ret; + } + + if (pd->domain_supply) { + ret = regulator_disable(pd->domain_supply); + if (ret) + return ret; + } + + return 0; +} + +static struct generic_pm_domain *apu_add_one_domain(struct apusys *apusys, + struct device_node *node) +{ + const struct apu_domain_data *domain_data; + struct apu_domain *pd; + int ret; + u32 id; + int i; + struct clk *clk; + + ret = of_property_read_u32(node, "reg", &id); + if (ret) { + dev_dbg(apusys->dev, "%pOF: invalid reg: %d\n", node, ret); + return ERR_PTR(ret); + } + + if (id >= apusys->data->num_domains) { + dev_dbg(apusys->dev, "%pOF: invalid id %d\n", node, id); + return ERR_PTR(-EINVAL); + } + + domain_data = &apusys->data->domains_data[id]; + + pd = devm_kzalloc(apusys->dev, sizeof(*pd), GFP_KERNEL); + if (!pd) + return ERR_PTR(-ENOMEM); + + pd->data = domain_data; + pd->apusys = apusys; + + pd->domain_supply = devm_regulator_get_optional(apusys->dev, "domain"); + if (IS_ERR(pd->domain_supply)) { + ret = PTR_ERR(pd->domain_supply); + if (ret != -EPROBE_DEFER) { + dev_dbg(apusys->dev, "domain_supply fail, ret=%d", ret); + apusys->vsram_supply = NULL; + } + return ERR_PTR(ret); + } + + /* + * For HW using ipu_if, the clock is switched to 26M + * when power down top domain and switch to default clock rate + * before power on. + */ + if (APU_PD_CAPS(pd, APU_PD_IPUIF_HW_CG)) { + pd->clk_top_conn = of_clk_get_by_name(node, "clk_top_conn"); + if (IS_ERR(pd->clk_top_conn)) { + dev_err(apusys->dev, "Fail to get clk_top_conn clock\n"); + ret = PTR_ERR(pd->clk_top_conn); + goto err_put_clocks; + } + + pd->clk_top_ipu_if = of_clk_get_by_name(node, "clk_top_ipu_if"); + if (IS_ERR(pd->clk_top_ipu_if)) { + dev_err(apusys->dev, "Fail to get clk_top_ipu_if clock\n"); + ret = PTR_ERR(pd->clk_top_ipu_if); + goto err_put_clocks; + } + + pd->clk_off = of_clk_get_by_name(node, "clk_off"); + if (IS_ERR(pd->clk_off)) { + dev_err(apusys->dev, "Fail to get clk_off clock\n"); + ret = PTR_ERR(pd->clk_off); + goto err_put_clocks; + } + + pd->clk_on_def = of_clk_get_by_name(node, "clk_on_default"); + if (IS_ERR(pd->clk_on_def)) { + dev_err(apusys->dev, "Fail to get clk_on_default clock\n"); + ret = PTR_ERR(pd->clk_on_def); + goto err_put_clocks; + } + } else { + pd->num_clks = of_clk_get_parent_count(node); + if (pd->num_clks > 0) { + pd->clks = devm_kcalloc(apusys->dev, pd->num_clks, + sizeof(*pd->clks), GFP_KERNEL); + if (!pd->clks) + return ERR_PTR(-ENOMEM); + } + + for (i = 0; i < pd->num_clks; i++) { + clk = of_clk_get(node, i); + if (IS_ERR(clk)) { + ret = PTR_ERR(clk); + dev_dbg(apusys->dev, + "%pOF: failed to get clk at index %d: %d\n", + node, i, ret); + goto err_put_clocks; + } + pd->clks[i].clk = clk; + } + } + + if (apusys->domains[id]) { + ret = -EEXIST; + dev_err(apusys->dev, "domain id %d already exists\n", id); + goto err_put_clocks; + } + + /* set rpc hw init status */ + ret = apu_top_init_hw(pd); + if (ret) { + dev_err(apusys->dev, "top init fail ret = %d\n", ret); + goto err_put_clocks; + } + + if (!pd->data->name) + pd->genpd.name = node->name; + else + pd->genpd.name = pd->data->name; + pd->genpd.power_off = apu_top_power_off; + pd->genpd.power_on = apu_top_power_on; + + ret = pm_genpd_init(&pd->genpd, NULL, true); + if (ret) { + dev_err(apusys->dev, "init power domain fail\n"); + goto err_put_clocks; + } + + apusys->domains[id] = &pd->genpd; + + return apusys->pd_data.domains[id]; + +err_put_clocks: + if (APU_PD_CAPS(pd, APU_PD_IPUIF_HW_CG)) { + clk_put(pd->clk_top_conn); + clk_put(pd->clk_top_ipu_if); + clk_put(pd->clk_off); + clk_put(pd->clk_on_def); + } else { + clk_bulk_put(pd->num_clks, pd->clks); + } + return ERR_PTR(ret); +} + +static void apu_remove_one_domain(struct apu_domain *pd) +{ + int ret; + + /* + * We're in the error cleanup already, so we only complain, + * but won't emit another error on top of the original one. + */ + ret = pm_genpd_remove(&pd->genpd); + if (ret) + dev_dbg(pd->apusys->dev, + "Remove domain '%s' : %d - state may be inconsistent\n", + pd->genpd.name, ret); + + if (APU_PD_CAPS(pd, APU_PD_IPUIF_HW_CG)) { + clk_put(pd->clk_top_conn); + clk_put(pd->clk_top_ipu_if); + clk_put(pd->clk_off); + clk_put(pd->clk_on_def); + } else { + clk_bulk_put(pd->num_clks, pd->clks); + } +} + +static void apu_domain_cleanup(struct apusys *apusys) +{ + struct generic_pm_domain *genpd; + struct apu_domain *pd; + int i; + + for (i = apusys->pd_data.num_domains - 1; i >= 0; i--) { + genpd = apusys->pd_data.domains[i]; + if (genpd) { + pd = to_apu_domain(genpd); + apu_remove_one_domain(pd); + } + } +} + +static const struct of_device_id apu_pm_of_match[] = { + { + .compatible = "mediatek,mt8192-apu-pm", + .data = &mt8192_apu_pm_data, + }, + { } +}; + +static int apu_pm_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + const struct apu_pm_data *data; + struct device_node *node; + struct apusys *apusys; + int ret; + + data = of_device_get_match_data(&pdev->dev); + if (!data) { + dev_dbg(dev, "no power domain data\n"); + return -EINVAL; + } + + apusys = devm_kzalloc(dev, + struct_size(apusys, domains, data->num_domains), + GFP_KERNEL); + if (!apusys) + return -ENOMEM; + + platform_set_drvdata(pdev, apusys); + apusys->dev = dev; + apusys->data = data; + apusys->pd_data.domains = apusys->domains; + apusys->pd_data.num_domains = data->num_domains; + + apusys->vsram_supply = devm_regulator_get_optional(dev, "vsram"); + if (IS_ERR(apusys->vsram_supply)) { + ret = PTR_ERR(apusys->vsram_supply); + if (ret != -EPROBE_DEFER) { + dev_err(dev, "vsram_supply fail, ret=%d", ret); + apusys->vsram_supply = NULL; + } + return ret; + } + + /* rpc */ + apusys->rpc = syscon_node_to_regmap(np); + if (IS_ERR(apusys->rpc)) { + dev_err(dev, "Unable to get rpc\n"); + return PTR_ERR(apusys->rpc); + } + + /* scpsys */ + apusys->scpsys = syscon_regmap_lookup_by_phandle(np, "mediatek,scpsys"); + if (IS_ERR(apusys->scpsys)) { + dev_err(dev, "Unable to get scpsys\n"); + return PTR_ERR(apusys->scpsys); + } + + /* apusys conn */ + apusys->conn = syscon_regmap_lookup_by_phandle_optional(np, "mediatek,apu-conn"); + if (IS_ERR(apusys->conn)) + dev_dbg(dev, "No optional phandle apu-conn\n"); + + /* apusys conn1 */ + apusys->conn1 = syscon_regmap_lookup_by_phandle_optional(np, "mediatek,apu-conn1"); + if (IS_ERR(apusys->conn1)) + dev_dbg(dev, "No optional phandle apu-conn1\n"); + + /* apusys vcore */ + apusys->vcore = syscon_regmap_lookup_by_phandle_optional(np, "mediatek,apu-vcore"); + if (IS_ERR(apusys->vcore)) + dev_dbg(dev, "No optional phandle apu-vcore\n"); + + for_each_available_child_of_node(np, node) { + struct generic_pm_domain *domain; + + domain = apu_add_one_domain(apusys, node); + if (IS_ERR(domain)) { + ret = PTR_ERR(domain); + of_node_put(node); + goto err_cleanup_domains; + } + } + + ret = of_genpd_add_provider_onecell(np, &apusys->pd_data); + if (ret) { + dev_dbg(dev, "failed to add provider: %d\n", ret); + goto err_cleanup_domains; + } + + return 0; + +err_cleanup_domains: + apu_domain_cleanup(apusys); + return ret; +} + +static int apu_pm_remove(struct platform_device *pdev) +{ + struct apusys *apusys = platform_get_drvdata(pdev); + + of_genpd_del_provider(pdev->dev.of_node); + apu_domain_cleanup(apusys); + + return 0; +} + +static struct platform_driver apu_pm_driver = { + .probe = apu_pm_probe, + .remove = apu_pm_remove, + .driver = { + .name = "mtk-apu-pm", + .suppress_bind_attrs = true, + .of_match_table = apu_pm_of_match, + }, +}; +module_platform_driver(apu_pm_driver); +MODULE_DESCRIPTION("APU Power Domain Driver"); +MODULE_LICENSE("GPL v2");