162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright (c) 2020 Collabora Ltd.
462306a36Sopenharmony_ci */
562306a36Sopenharmony_ci#include <linux/clk.h>
662306a36Sopenharmony_ci#include <linux/clk-provider.h>
762306a36Sopenharmony_ci#include <linux/init.h>
862306a36Sopenharmony_ci#include <linux/io.h>
962306a36Sopenharmony_ci#include <linux/iopoll.h>
1062306a36Sopenharmony_ci#include <linux/mfd/syscon.h>
1162306a36Sopenharmony_ci#include <linux/of.h>
1262306a36Sopenharmony_ci#include <linux/of_clk.h>
1362306a36Sopenharmony_ci#include <linux/platform_device.h>
1462306a36Sopenharmony_ci#include <linux/pm_domain.h>
1562306a36Sopenharmony_ci#include <linux/regmap.h>
1662306a36Sopenharmony_ci#include <linux/regulator/consumer.h>
1762306a36Sopenharmony_ci#include <linux/soc/mediatek/infracfg.h>
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_ci#include "mt6795-pm-domains.h"
2062306a36Sopenharmony_ci#include "mt8167-pm-domains.h"
2162306a36Sopenharmony_ci#include "mt8173-pm-domains.h"
2262306a36Sopenharmony_ci#include "mt8183-pm-domains.h"
2362306a36Sopenharmony_ci#include "mt8186-pm-domains.h"
2462306a36Sopenharmony_ci#include "mt8188-pm-domains.h"
2562306a36Sopenharmony_ci#include "mt8192-pm-domains.h"
2662306a36Sopenharmony_ci#include "mt8195-pm-domains.h"
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_ci#define MTK_POLL_DELAY_US		10
2962306a36Sopenharmony_ci#define MTK_POLL_TIMEOUT		USEC_PER_SEC
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_ci#define PWR_RST_B_BIT			BIT(0)
3262306a36Sopenharmony_ci#define PWR_ISO_BIT			BIT(1)
3362306a36Sopenharmony_ci#define PWR_ON_BIT			BIT(2)
3462306a36Sopenharmony_ci#define PWR_ON_2ND_BIT			BIT(3)
3562306a36Sopenharmony_ci#define PWR_CLK_DIS_BIT			BIT(4)
3662306a36Sopenharmony_ci#define PWR_SRAM_CLKISO_BIT		BIT(5)
3762306a36Sopenharmony_ci#define PWR_SRAM_ISOINT_B_BIT		BIT(6)
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_cistruct scpsys_domain {
4062306a36Sopenharmony_ci	struct generic_pm_domain genpd;
4162306a36Sopenharmony_ci	const struct scpsys_domain_data *data;
4262306a36Sopenharmony_ci	struct scpsys *scpsys;
4362306a36Sopenharmony_ci	int num_clks;
4462306a36Sopenharmony_ci	struct clk_bulk_data *clks;
4562306a36Sopenharmony_ci	int num_subsys_clks;
4662306a36Sopenharmony_ci	struct clk_bulk_data *subsys_clks;
4762306a36Sopenharmony_ci	struct regmap *infracfg;
4862306a36Sopenharmony_ci	struct regmap *smi;
4962306a36Sopenharmony_ci	struct regulator *supply;
5062306a36Sopenharmony_ci};
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_cistruct scpsys {
5362306a36Sopenharmony_ci	struct device *dev;
5462306a36Sopenharmony_ci	struct regmap *base;
5562306a36Sopenharmony_ci	const struct scpsys_soc_data *soc_data;
5662306a36Sopenharmony_ci	struct genpd_onecell_data pd_data;
5762306a36Sopenharmony_ci	struct generic_pm_domain *domains[];
5862306a36Sopenharmony_ci};
5962306a36Sopenharmony_ci
6062306a36Sopenharmony_ci#define to_scpsys_domain(gpd) container_of(gpd, struct scpsys_domain, genpd)
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_cistatic bool scpsys_domain_is_on(struct scpsys_domain *pd)
6362306a36Sopenharmony_ci{
6462306a36Sopenharmony_ci	struct scpsys *scpsys = pd->scpsys;
6562306a36Sopenharmony_ci	u32 status, status2;
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_ci	regmap_read(scpsys->base, pd->data->pwr_sta_offs, &status);
6862306a36Sopenharmony_ci	status &= pd->data->sta_mask;
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_ci	regmap_read(scpsys->base, pd->data->pwr_sta2nd_offs, &status2);
7162306a36Sopenharmony_ci	status2 &= pd->data->sta_mask;
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_ci	/* A domain is on when both status bits are set. */
7462306a36Sopenharmony_ci	return status && status2;
7562306a36Sopenharmony_ci}
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_cistatic int scpsys_sram_enable(struct scpsys_domain *pd)
7862306a36Sopenharmony_ci{
7962306a36Sopenharmony_ci	u32 pdn_ack = pd->data->sram_pdn_ack_bits;
8062306a36Sopenharmony_ci	struct scpsys *scpsys = pd->scpsys;
8162306a36Sopenharmony_ci	unsigned int tmp;
8262306a36Sopenharmony_ci	int ret;
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_ci	regmap_clear_bits(scpsys->base, pd->data->ctl_offs, pd->data->sram_pdn_bits);
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_ci	/* Either wait until SRAM_PDN_ACK all 1 or 0 */
8762306a36Sopenharmony_ci	ret = regmap_read_poll_timeout(scpsys->base, pd->data->ctl_offs, tmp,
8862306a36Sopenharmony_ci				       (tmp & pdn_ack) == 0, MTK_POLL_DELAY_US, MTK_POLL_TIMEOUT);
8962306a36Sopenharmony_ci	if (ret < 0)
9062306a36Sopenharmony_ci		return ret;
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_ci	if (MTK_SCPD_CAPS(pd, MTK_SCPD_SRAM_ISO)) {
9362306a36Sopenharmony_ci		regmap_set_bits(scpsys->base, pd->data->ctl_offs, PWR_SRAM_ISOINT_B_BIT);
9462306a36Sopenharmony_ci		udelay(1);
9562306a36Sopenharmony_ci		regmap_clear_bits(scpsys->base, pd->data->ctl_offs, PWR_SRAM_CLKISO_BIT);
9662306a36Sopenharmony_ci	}
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_ci	return 0;
9962306a36Sopenharmony_ci}
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_cistatic int scpsys_sram_disable(struct scpsys_domain *pd)
10262306a36Sopenharmony_ci{
10362306a36Sopenharmony_ci	u32 pdn_ack = pd->data->sram_pdn_ack_bits;
10462306a36Sopenharmony_ci	struct scpsys *scpsys = pd->scpsys;
10562306a36Sopenharmony_ci	unsigned int tmp;
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_ci	if (MTK_SCPD_CAPS(pd, MTK_SCPD_SRAM_ISO)) {
10862306a36Sopenharmony_ci		regmap_set_bits(scpsys->base, pd->data->ctl_offs, PWR_SRAM_CLKISO_BIT);
10962306a36Sopenharmony_ci		udelay(1);
11062306a36Sopenharmony_ci		regmap_clear_bits(scpsys->base, pd->data->ctl_offs, PWR_SRAM_ISOINT_B_BIT);
11162306a36Sopenharmony_ci	}
11262306a36Sopenharmony_ci
11362306a36Sopenharmony_ci	regmap_set_bits(scpsys->base, pd->data->ctl_offs, pd->data->sram_pdn_bits);
11462306a36Sopenharmony_ci
11562306a36Sopenharmony_ci	/* Either wait until SRAM_PDN_ACK all 1 or 0 */
11662306a36Sopenharmony_ci	return regmap_read_poll_timeout(scpsys->base, pd->data->ctl_offs, tmp,
11762306a36Sopenharmony_ci					(tmp & pdn_ack) == pdn_ack, MTK_POLL_DELAY_US,
11862306a36Sopenharmony_ci					MTK_POLL_TIMEOUT);
11962306a36Sopenharmony_ci}
12062306a36Sopenharmony_ci
12162306a36Sopenharmony_cistatic int _scpsys_bus_protect_enable(const struct scpsys_bus_prot_data *bpd, struct regmap *regmap)
12262306a36Sopenharmony_ci{
12362306a36Sopenharmony_ci	int i, ret;
12462306a36Sopenharmony_ci
12562306a36Sopenharmony_ci	for (i = 0; i < SPM_MAX_BUS_PROT_DATA; i++) {
12662306a36Sopenharmony_ci		u32 val, mask = bpd[i].bus_prot_mask;
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_ci		if (!mask)
12962306a36Sopenharmony_ci			break;
13062306a36Sopenharmony_ci
13162306a36Sopenharmony_ci		if (bpd[i].bus_prot_reg_update)
13262306a36Sopenharmony_ci			regmap_set_bits(regmap, bpd[i].bus_prot_set, mask);
13362306a36Sopenharmony_ci		else
13462306a36Sopenharmony_ci			regmap_write(regmap, bpd[i].bus_prot_set, mask);
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_ci		ret = regmap_read_poll_timeout(regmap, bpd[i].bus_prot_sta,
13762306a36Sopenharmony_ci					       val, (val & mask) == mask,
13862306a36Sopenharmony_ci					       MTK_POLL_DELAY_US, MTK_POLL_TIMEOUT);
13962306a36Sopenharmony_ci		if (ret)
14062306a36Sopenharmony_ci			return ret;
14162306a36Sopenharmony_ci	}
14262306a36Sopenharmony_ci
14362306a36Sopenharmony_ci	return 0;
14462306a36Sopenharmony_ci}
14562306a36Sopenharmony_ci
14662306a36Sopenharmony_cistatic int scpsys_bus_protect_enable(struct scpsys_domain *pd)
14762306a36Sopenharmony_ci{
14862306a36Sopenharmony_ci	int ret;
14962306a36Sopenharmony_ci
15062306a36Sopenharmony_ci	ret = _scpsys_bus_protect_enable(pd->data->bp_infracfg, pd->infracfg);
15162306a36Sopenharmony_ci	if (ret)
15262306a36Sopenharmony_ci		return ret;
15362306a36Sopenharmony_ci
15462306a36Sopenharmony_ci	return _scpsys_bus_protect_enable(pd->data->bp_smi, pd->smi);
15562306a36Sopenharmony_ci}
15662306a36Sopenharmony_ci
15762306a36Sopenharmony_cistatic int _scpsys_bus_protect_disable(const struct scpsys_bus_prot_data *bpd,
15862306a36Sopenharmony_ci				       struct regmap *regmap)
15962306a36Sopenharmony_ci{
16062306a36Sopenharmony_ci	int i, ret;
16162306a36Sopenharmony_ci
16262306a36Sopenharmony_ci	for (i = SPM_MAX_BUS_PROT_DATA - 1; i >= 0; i--) {
16362306a36Sopenharmony_ci		u32 val, mask = bpd[i].bus_prot_mask;
16462306a36Sopenharmony_ci
16562306a36Sopenharmony_ci		if (!mask)
16662306a36Sopenharmony_ci			continue;
16762306a36Sopenharmony_ci
16862306a36Sopenharmony_ci		if (bpd[i].bus_prot_reg_update)
16962306a36Sopenharmony_ci			regmap_clear_bits(regmap, bpd[i].bus_prot_clr, mask);
17062306a36Sopenharmony_ci		else
17162306a36Sopenharmony_ci			regmap_write(regmap, bpd[i].bus_prot_clr, mask);
17262306a36Sopenharmony_ci
17362306a36Sopenharmony_ci		if (bpd[i].ignore_clr_ack)
17462306a36Sopenharmony_ci			continue;
17562306a36Sopenharmony_ci
17662306a36Sopenharmony_ci		ret = regmap_read_poll_timeout(regmap, bpd[i].bus_prot_sta,
17762306a36Sopenharmony_ci					       val, !(val & mask),
17862306a36Sopenharmony_ci					       MTK_POLL_DELAY_US, MTK_POLL_TIMEOUT);
17962306a36Sopenharmony_ci		if (ret)
18062306a36Sopenharmony_ci			return ret;
18162306a36Sopenharmony_ci	}
18262306a36Sopenharmony_ci
18362306a36Sopenharmony_ci	return 0;
18462306a36Sopenharmony_ci}
18562306a36Sopenharmony_ci
18662306a36Sopenharmony_cistatic int scpsys_bus_protect_disable(struct scpsys_domain *pd)
18762306a36Sopenharmony_ci{
18862306a36Sopenharmony_ci	int ret;
18962306a36Sopenharmony_ci
19062306a36Sopenharmony_ci	ret = _scpsys_bus_protect_disable(pd->data->bp_smi, pd->smi);
19162306a36Sopenharmony_ci	if (ret)
19262306a36Sopenharmony_ci		return ret;
19362306a36Sopenharmony_ci
19462306a36Sopenharmony_ci	return _scpsys_bus_protect_disable(pd->data->bp_infracfg, pd->infracfg);
19562306a36Sopenharmony_ci}
19662306a36Sopenharmony_ci
19762306a36Sopenharmony_cistatic int scpsys_regulator_enable(struct regulator *supply)
19862306a36Sopenharmony_ci{
19962306a36Sopenharmony_ci	return supply ? regulator_enable(supply) : 0;
20062306a36Sopenharmony_ci}
20162306a36Sopenharmony_ci
20262306a36Sopenharmony_cistatic int scpsys_regulator_disable(struct regulator *supply)
20362306a36Sopenharmony_ci{
20462306a36Sopenharmony_ci	return supply ? regulator_disable(supply) : 0;
20562306a36Sopenharmony_ci}
20662306a36Sopenharmony_ci
20762306a36Sopenharmony_cistatic int scpsys_power_on(struct generic_pm_domain *genpd)
20862306a36Sopenharmony_ci{
20962306a36Sopenharmony_ci	struct scpsys_domain *pd = container_of(genpd, struct scpsys_domain, genpd);
21062306a36Sopenharmony_ci	struct scpsys *scpsys = pd->scpsys;
21162306a36Sopenharmony_ci	bool tmp;
21262306a36Sopenharmony_ci	int ret;
21362306a36Sopenharmony_ci
21462306a36Sopenharmony_ci	ret = scpsys_regulator_enable(pd->supply);
21562306a36Sopenharmony_ci	if (ret)
21662306a36Sopenharmony_ci		return ret;
21762306a36Sopenharmony_ci
21862306a36Sopenharmony_ci	ret = clk_bulk_prepare_enable(pd->num_clks, pd->clks);
21962306a36Sopenharmony_ci	if (ret)
22062306a36Sopenharmony_ci		goto err_reg;
22162306a36Sopenharmony_ci
22262306a36Sopenharmony_ci	if (pd->data->ext_buck_iso_offs && MTK_SCPD_CAPS(pd, MTK_SCPD_EXT_BUCK_ISO))
22362306a36Sopenharmony_ci		regmap_clear_bits(scpsys->base, pd->data->ext_buck_iso_offs,
22462306a36Sopenharmony_ci				  pd->data->ext_buck_iso_mask);
22562306a36Sopenharmony_ci
22662306a36Sopenharmony_ci	/* subsys power on */
22762306a36Sopenharmony_ci	regmap_set_bits(scpsys->base, pd->data->ctl_offs, PWR_ON_BIT);
22862306a36Sopenharmony_ci	regmap_set_bits(scpsys->base, pd->data->ctl_offs, PWR_ON_2ND_BIT);
22962306a36Sopenharmony_ci
23062306a36Sopenharmony_ci	/* wait until PWR_ACK = 1 */
23162306a36Sopenharmony_ci	ret = readx_poll_timeout(scpsys_domain_is_on, pd, tmp, tmp, MTK_POLL_DELAY_US,
23262306a36Sopenharmony_ci				 MTK_POLL_TIMEOUT);
23362306a36Sopenharmony_ci	if (ret < 0)
23462306a36Sopenharmony_ci		goto err_pwr_ack;
23562306a36Sopenharmony_ci
23662306a36Sopenharmony_ci	regmap_clear_bits(scpsys->base, pd->data->ctl_offs, PWR_CLK_DIS_BIT);
23762306a36Sopenharmony_ci	regmap_clear_bits(scpsys->base, pd->data->ctl_offs, PWR_ISO_BIT);
23862306a36Sopenharmony_ci	regmap_set_bits(scpsys->base, pd->data->ctl_offs, PWR_RST_B_BIT);
23962306a36Sopenharmony_ci
24062306a36Sopenharmony_ci	ret = clk_bulk_prepare_enable(pd->num_subsys_clks, pd->subsys_clks);
24162306a36Sopenharmony_ci	if (ret)
24262306a36Sopenharmony_ci		goto err_pwr_ack;
24362306a36Sopenharmony_ci
24462306a36Sopenharmony_ci	ret = scpsys_sram_enable(pd);
24562306a36Sopenharmony_ci	if (ret < 0)
24662306a36Sopenharmony_ci		goto err_disable_subsys_clks;
24762306a36Sopenharmony_ci
24862306a36Sopenharmony_ci	ret = scpsys_bus_protect_disable(pd);
24962306a36Sopenharmony_ci	if (ret < 0)
25062306a36Sopenharmony_ci		goto err_disable_sram;
25162306a36Sopenharmony_ci
25262306a36Sopenharmony_ci	return 0;
25362306a36Sopenharmony_ci
25462306a36Sopenharmony_cierr_disable_sram:
25562306a36Sopenharmony_ci	scpsys_sram_disable(pd);
25662306a36Sopenharmony_cierr_disable_subsys_clks:
25762306a36Sopenharmony_ci	clk_bulk_disable_unprepare(pd->num_subsys_clks, pd->subsys_clks);
25862306a36Sopenharmony_cierr_pwr_ack:
25962306a36Sopenharmony_ci	clk_bulk_disable_unprepare(pd->num_clks, pd->clks);
26062306a36Sopenharmony_cierr_reg:
26162306a36Sopenharmony_ci	scpsys_regulator_disable(pd->supply);
26262306a36Sopenharmony_ci	return ret;
26362306a36Sopenharmony_ci}
26462306a36Sopenharmony_ci
26562306a36Sopenharmony_cistatic int scpsys_power_off(struct generic_pm_domain *genpd)
26662306a36Sopenharmony_ci{
26762306a36Sopenharmony_ci	struct scpsys_domain *pd = container_of(genpd, struct scpsys_domain, genpd);
26862306a36Sopenharmony_ci	struct scpsys *scpsys = pd->scpsys;
26962306a36Sopenharmony_ci	bool tmp;
27062306a36Sopenharmony_ci	int ret;
27162306a36Sopenharmony_ci
27262306a36Sopenharmony_ci	ret = scpsys_bus_protect_enable(pd);
27362306a36Sopenharmony_ci	if (ret < 0)
27462306a36Sopenharmony_ci		return ret;
27562306a36Sopenharmony_ci
27662306a36Sopenharmony_ci	ret = scpsys_sram_disable(pd);
27762306a36Sopenharmony_ci	if (ret < 0)
27862306a36Sopenharmony_ci		return ret;
27962306a36Sopenharmony_ci
28062306a36Sopenharmony_ci	if (pd->data->ext_buck_iso_offs && MTK_SCPD_CAPS(pd, MTK_SCPD_EXT_BUCK_ISO))
28162306a36Sopenharmony_ci		regmap_set_bits(scpsys->base, pd->data->ext_buck_iso_offs,
28262306a36Sopenharmony_ci				pd->data->ext_buck_iso_mask);
28362306a36Sopenharmony_ci
28462306a36Sopenharmony_ci	clk_bulk_disable_unprepare(pd->num_subsys_clks, pd->subsys_clks);
28562306a36Sopenharmony_ci
28662306a36Sopenharmony_ci	/* subsys power off */
28762306a36Sopenharmony_ci	regmap_set_bits(scpsys->base, pd->data->ctl_offs, PWR_ISO_BIT);
28862306a36Sopenharmony_ci	regmap_set_bits(scpsys->base, pd->data->ctl_offs, PWR_CLK_DIS_BIT);
28962306a36Sopenharmony_ci	regmap_clear_bits(scpsys->base, pd->data->ctl_offs, PWR_RST_B_BIT);
29062306a36Sopenharmony_ci	regmap_clear_bits(scpsys->base, pd->data->ctl_offs, PWR_ON_2ND_BIT);
29162306a36Sopenharmony_ci	regmap_clear_bits(scpsys->base, pd->data->ctl_offs, PWR_ON_BIT);
29262306a36Sopenharmony_ci
29362306a36Sopenharmony_ci	/* wait until PWR_ACK = 0 */
29462306a36Sopenharmony_ci	ret = readx_poll_timeout(scpsys_domain_is_on, pd, tmp, !tmp, MTK_POLL_DELAY_US,
29562306a36Sopenharmony_ci				 MTK_POLL_TIMEOUT);
29662306a36Sopenharmony_ci	if (ret < 0)
29762306a36Sopenharmony_ci		return ret;
29862306a36Sopenharmony_ci
29962306a36Sopenharmony_ci	clk_bulk_disable_unprepare(pd->num_clks, pd->clks);
30062306a36Sopenharmony_ci
30162306a36Sopenharmony_ci	scpsys_regulator_disable(pd->supply);
30262306a36Sopenharmony_ci
30362306a36Sopenharmony_ci	return 0;
30462306a36Sopenharmony_ci}
30562306a36Sopenharmony_ci
30662306a36Sopenharmony_cistatic struct
30762306a36Sopenharmony_cigeneric_pm_domain *scpsys_add_one_domain(struct scpsys *scpsys, struct device_node *node)
30862306a36Sopenharmony_ci{
30962306a36Sopenharmony_ci	const struct scpsys_domain_data *domain_data;
31062306a36Sopenharmony_ci	struct scpsys_domain *pd;
31162306a36Sopenharmony_ci	struct device_node *root_node = scpsys->dev->of_node;
31262306a36Sopenharmony_ci	struct device_node *smi_node;
31362306a36Sopenharmony_ci	struct property *prop;
31462306a36Sopenharmony_ci	const char *clk_name;
31562306a36Sopenharmony_ci	int i, ret, num_clks;
31662306a36Sopenharmony_ci	struct clk *clk;
31762306a36Sopenharmony_ci	int clk_ind = 0;
31862306a36Sopenharmony_ci	u32 id;
31962306a36Sopenharmony_ci
32062306a36Sopenharmony_ci	ret = of_property_read_u32(node, "reg", &id);
32162306a36Sopenharmony_ci	if (ret) {
32262306a36Sopenharmony_ci		dev_err(scpsys->dev, "%pOF: failed to retrieve domain id from reg: %d\n",
32362306a36Sopenharmony_ci			node, ret);
32462306a36Sopenharmony_ci		return ERR_PTR(-EINVAL);
32562306a36Sopenharmony_ci	}
32662306a36Sopenharmony_ci
32762306a36Sopenharmony_ci	if (id >= scpsys->soc_data->num_domains) {
32862306a36Sopenharmony_ci		dev_err(scpsys->dev, "%pOF: invalid domain id %d\n", node, id);
32962306a36Sopenharmony_ci		return ERR_PTR(-EINVAL);
33062306a36Sopenharmony_ci	}
33162306a36Sopenharmony_ci
33262306a36Sopenharmony_ci	domain_data = &scpsys->soc_data->domains_data[id];
33362306a36Sopenharmony_ci	if (domain_data->sta_mask == 0) {
33462306a36Sopenharmony_ci		dev_err(scpsys->dev, "%pOF: undefined domain id %d\n", node, id);
33562306a36Sopenharmony_ci		return ERR_PTR(-EINVAL);
33662306a36Sopenharmony_ci	}
33762306a36Sopenharmony_ci
33862306a36Sopenharmony_ci	pd = devm_kzalloc(scpsys->dev, sizeof(*pd), GFP_KERNEL);
33962306a36Sopenharmony_ci	if (!pd)
34062306a36Sopenharmony_ci		return ERR_PTR(-ENOMEM);
34162306a36Sopenharmony_ci
34262306a36Sopenharmony_ci	pd->data = domain_data;
34362306a36Sopenharmony_ci	pd->scpsys = scpsys;
34462306a36Sopenharmony_ci
34562306a36Sopenharmony_ci	if (MTK_SCPD_CAPS(pd, MTK_SCPD_DOMAIN_SUPPLY)) {
34662306a36Sopenharmony_ci		/*
34762306a36Sopenharmony_ci		 * Find regulator in current power domain node.
34862306a36Sopenharmony_ci		 * devm_regulator_get() finds regulator in a node and its child
34962306a36Sopenharmony_ci		 * node, so set of_node to current power domain node then change
35062306a36Sopenharmony_ci		 * back to original node after regulator is found for current
35162306a36Sopenharmony_ci		 * power domain node.
35262306a36Sopenharmony_ci		 */
35362306a36Sopenharmony_ci		scpsys->dev->of_node = node;
35462306a36Sopenharmony_ci		pd->supply = devm_regulator_get(scpsys->dev, "domain");
35562306a36Sopenharmony_ci		scpsys->dev->of_node = root_node;
35662306a36Sopenharmony_ci		if (IS_ERR(pd->supply)) {
35762306a36Sopenharmony_ci			dev_err_probe(scpsys->dev, PTR_ERR(pd->supply),
35862306a36Sopenharmony_ci				      "%pOF: failed to get power supply.\n",
35962306a36Sopenharmony_ci				      node);
36062306a36Sopenharmony_ci			return ERR_CAST(pd->supply);
36162306a36Sopenharmony_ci		}
36262306a36Sopenharmony_ci	}
36362306a36Sopenharmony_ci
36462306a36Sopenharmony_ci	pd->infracfg = syscon_regmap_lookup_by_phandle_optional(node, "mediatek,infracfg");
36562306a36Sopenharmony_ci	if (IS_ERR(pd->infracfg))
36662306a36Sopenharmony_ci		return ERR_CAST(pd->infracfg);
36762306a36Sopenharmony_ci
36862306a36Sopenharmony_ci	smi_node = of_parse_phandle(node, "mediatek,smi", 0);
36962306a36Sopenharmony_ci	if (smi_node) {
37062306a36Sopenharmony_ci		pd->smi = device_node_to_regmap(smi_node);
37162306a36Sopenharmony_ci		of_node_put(smi_node);
37262306a36Sopenharmony_ci		if (IS_ERR(pd->smi))
37362306a36Sopenharmony_ci			return ERR_CAST(pd->smi);
37462306a36Sopenharmony_ci	}
37562306a36Sopenharmony_ci
37662306a36Sopenharmony_ci	num_clks = of_clk_get_parent_count(node);
37762306a36Sopenharmony_ci	if (num_clks > 0) {
37862306a36Sopenharmony_ci		/* Calculate number of subsys_clks */
37962306a36Sopenharmony_ci		of_property_for_each_string(node, "clock-names", prop, clk_name) {
38062306a36Sopenharmony_ci			char *subsys;
38162306a36Sopenharmony_ci
38262306a36Sopenharmony_ci			subsys = strchr(clk_name, '-');
38362306a36Sopenharmony_ci			if (subsys)
38462306a36Sopenharmony_ci				pd->num_subsys_clks++;
38562306a36Sopenharmony_ci			else
38662306a36Sopenharmony_ci				pd->num_clks++;
38762306a36Sopenharmony_ci		}
38862306a36Sopenharmony_ci
38962306a36Sopenharmony_ci		pd->clks = devm_kcalloc(scpsys->dev, pd->num_clks, sizeof(*pd->clks), GFP_KERNEL);
39062306a36Sopenharmony_ci		if (!pd->clks)
39162306a36Sopenharmony_ci			return ERR_PTR(-ENOMEM);
39262306a36Sopenharmony_ci
39362306a36Sopenharmony_ci		pd->subsys_clks = devm_kcalloc(scpsys->dev, pd->num_subsys_clks,
39462306a36Sopenharmony_ci					       sizeof(*pd->subsys_clks), GFP_KERNEL);
39562306a36Sopenharmony_ci		if (!pd->subsys_clks)
39662306a36Sopenharmony_ci			return ERR_PTR(-ENOMEM);
39762306a36Sopenharmony_ci
39862306a36Sopenharmony_ci	}
39962306a36Sopenharmony_ci
40062306a36Sopenharmony_ci	for (i = 0; i < pd->num_clks; i++) {
40162306a36Sopenharmony_ci		clk = of_clk_get(node, i);
40262306a36Sopenharmony_ci		if (IS_ERR(clk)) {
40362306a36Sopenharmony_ci			ret = PTR_ERR(clk);
40462306a36Sopenharmony_ci			dev_err_probe(scpsys->dev, ret,
40562306a36Sopenharmony_ci				      "%pOF: failed to get clk at index %d\n", node, i);
40662306a36Sopenharmony_ci			goto err_put_clocks;
40762306a36Sopenharmony_ci		}
40862306a36Sopenharmony_ci
40962306a36Sopenharmony_ci		pd->clks[clk_ind++].clk = clk;
41062306a36Sopenharmony_ci	}
41162306a36Sopenharmony_ci
41262306a36Sopenharmony_ci	for (i = 0; i < pd->num_subsys_clks; i++) {
41362306a36Sopenharmony_ci		clk = of_clk_get(node, i + clk_ind);
41462306a36Sopenharmony_ci		if (IS_ERR(clk)) {
41562306a36Sopenharmony_ci			ret = PTR_ERR(clk);
41662306a36Sopenharmony_ci			dev_err_probe(scpsys->dev, ret,
41762306a36Sopenharmony_ci				      "%pOF: failed to get clk at index %d\n", node,
41862306a36Sopenharmony_ci				      i + clk_ind);
41962306a36Sopenharmony_ci			goto err_put_subsys_clocks;
42062306a36Sopenharmony_ci		}
42162306a36Sopenharmony_ci
42262306a36Sopenharmony_ci		pd->subsys_clks[i].clk = clk;
42362306a36Sopenharmony_ci	}
42462306a36Sopenharmony_ci
42562306a36Sopenharmony_ci	/*
42662306a36Sopenharmony_ci	 * Initially turn on all domains to make the domains usable
42762306a36Sopenharmony_ci	 * with !CONFIG_PM and to get the hardware in sync with the
42862306a36Sopenharmony_ci	 * software.  The unused domains will be switched off during
42962306a36Sopenharmony_ci	 * late_init time.
43062306a36Sopenharmony_ci	 */
43162306a36Sopenharmony_ci	if (MTK_SCPD_CAPS(pd, MTK_SCPD_KEEP_DEFAULT_OFF)) {
43262306a36Sopenharmony_ci		if (scpsys_domain_is_on(pd))
43362306a36Sopenharmony_ci			dev_warn(scpsys->dev,
43462306a36Sopenharmony_ci				 "%pOF: A default off power domain has been ON\n", node);
43562306a36Sopenharmony_ci	} else {
43662306a36Sopenharmony_ci		ret = scpsys_power_on(&pd->genpd);
43762306a36Sopenharmony_ci		if (ret < 0) {
43862306a36Sopenharmony_ci			dev_err(scpsys->dev, "%pOF: failed to power on domain: %d\n", node, ret);
43962306a36Sopenharmony_ci			goto err_put_subsys_clocks;
44062306a36Sopenharmony_ci		}
44162306a36Sopenharmony_ci
44262306a36Sopenharmony_ci		if (MTK_SCPD_CAPS(pd, MTK_SCPD_ALWAYS_ON))
44362306a36Sopenharmony_ci			pd->genpd.flags |= GENPD_FLAG_ALWAYS_ON;
44462306a36Sopenharmony_ci	}
44562306a36Sopenharmony_ci
44662306a36Sopenharmony_ci	if (scpsys->domains[id]) {
44762306a36Sopenharmony_ci		ret = -EINVAL;
44862306a36Sopenharmony_ci		dev_err(scpsys->dev,
44962306a36Sopenharmony_ci			"power domain with id %d already exists, check your device-tree\n", id);
45062306a36Sopenharmony_ci		goto err_put_subsys_clocks;
45162306a36Sopenharmony_ci	}
45262306a36Sopenharmony_ci
45362306a36Sopenharmony_ci	if (!pd->data->name)
45462306a36Sopenharmony_ci		pd->genpd.name = node->name;
45562306a36Sopenharmony_ci	else
45662306a36Sopenharmony_ci		pd->genpd.name = pd->data->name;
45762306a36Sopenharmony_ci
45862306a36Sopenharmony_ci	pd->genpd.power_off = scpsys_power_off;
45962306a36Sopenharmony_ci	pd->genpd.power_on = scpsys_power_on;
46062306a36Sopenharmony_ci
46162306a36Sopenharmony_ci	if (MTK_SCPD_CAPS(pd, MTK_SCPD_ACTIVE_WAKEUP))
46262306a36Sopenharmony_ci		pd->genpd.flags |= GENPD_FLAG_ACTIVE_WAKEUP;
46362306a36Sopenharmony_ci
46462306a36Sopenharmony_ci	if (MTK_SCPD_CAPS(pd, MTK_SCPD_KEEP_DEFAULT_OFF))
46562306a36Sopenharmony_ci		pm_genpd_init(&pd->genpd, NULL, true);
46662306a36Sopenharmony_ci	else
46762306a36Sopenharmony_ci		pm_genpd_init(&pd->genpd, NULL, false);
46862306a36Sopenharmony_ci
46962306a36Sopenharmony_ci	scpsys->domains[id] = &pd->genpd;
47062306a36Sopenharmony_ci
47162306a36Sopenharmony_ci	return scpsys->pd_data.domains[id];
47262306a36Sopenharmony_ci
47362306a36Sopenharmony_cierr_put_subsys_clocks:
47462306a36Sopenharmony_ci	clk_bulk_put(pd->num_subsys_clks, pd->subsys_clks);
47562306a36Sopenharmony_cierr_put_clocks:
47662306a36Sopenharmony_ci	clk_bulk_put(pd->num_clks, pd->clks);
47762306a36Sopenharmony_ci	return ERR_PTR(ret);
47862306a36Sopenharmony_ci}
47962306a36Sopenharmony_ci
48062306a36Sopenharmony_cistatic int scpsys_add_subdomain(struct scpsys *scpsys, struct device_node *parent)
48162306a36Sopenharmony_ci{
48262306a36Sopenharmony_ci	struct generic_pm_domain *child_pd, *parent_pd;
48362306a36Sopenharmony_ci	struct device_node *child;
48462306a36Sopenharmony_ci	int ret;
48562306a36Sopenharmony_ci
48662306a36Sopenharmony_ci	for_each_child_of_node(parent, child) {
48762306a36Sopenharmony_ci		u32 id;
48862306a36Sopenharmony_ci
48962306a36Sopenharmony_ci		ret = of_property_read_u32(parent, "reg", &id);
49062306a36Sopenharmony_ci		if (ret) {
49162306a36Sopenharmony_ci			dev_err(scpsys->dev, "%pOF: failed to get parent domain id\n", child);
49262306a36Sopenharmony_ci			goto err_put_node;
49362306a36Sopenharmony_ci		}
49462306a36Sopenharmony_ci
49562306a36Sopenharmony_ci		if (!scpsys->pd_data.domains[id]) {
49662306a36Sopenharmony_ci			ret = -EINVAL;
49762306a36Sopenharmony_ci			dev_err(scpsys->dev, "power domain with id %d does not exist\n", id);
49862306a36Sopenharmony_ci			goto err_put_node;
49962306a36Sopenharmony_ci		}
50062306a36Sopenharmony_ci
50162306a36Sopenharmony_ci		parent_pd = scpsys->pd_data.domains[id];
50262306a36Sopenharmony_ci
50362306a36Sopenharmony_ci		child_pd = scpsys_add_one_domain(scpsys, child);
50462306a36Sopenharmony_ci		if (IS_ERR(child_pd)) {
50562306a36Sopenharmony_ci			ret = PTR_ERR(child_pd);
50662306a36Sopenharmony_ci			dev_err_probe(scpsys->dev, ret, "%pOF: failed to get child domain id\n",
50762306a36Sopenharmony_ci				      child);
50862306a36Sopenharmony_ci			goto err_put_node;
50962306a36Sopenharmony_ci		}
51062306a36Sopenharmony_ci
51162306a36Sopenharmony_ci		/* recursive call to add all subdomains */
51262306a36Sopenharmony_ci		ret = scpsys_add_subdomain(scpsys, child);
51362306a36Sopenharmony_ci		if (ret)
51462306a36Sopenharmony_ci			goto err_put_node;
51562306a36Sopenharmony_ci
51662306a36Sopenharmony_ci		ret = pm_genpd_add_subdomain(parent_pd, child_pd);
51762306a36Sopenharmony_ci		if (ret) {
51862306a36Sopenharmony_ci			dev_err(scpsys->dev, "failed to add %s subdomain to parent %s\n",
51962306a36Sopenharmony_ci				child_pd->name, parent_pd->name);
52062306a36Sopenharmony_ci			goto err_put_node;
52162306a36Sopenharmony_ci		} else {
52262306a36Sopenharmony_ci			dev_dbg(scpsys->dev, "%s add subdomain: %s\n", parent_pd->name,
52362306a36Sopenharmony_ci				child_pd->name);
52462306a36Sopenharmony_ci		}
52562306a36Sopenharmony_ci	}
52662306a36Sopenharmony_ci
52762306a36Sopenharmony_ci	return 0;
52862306a36Sopenharmony_ci
52962306a36Sopenharmony_cierr_put_node:
53062306a36Sopenharmony_ci	of_node_put(child);
53162306a36Sopenharmony_ci	return ret;
53262306a36Sopenharmony_ci}
53362306a36Sopenharmony_ci
53462306a36Sopenharmony_cistatic void scpsys_remove_one_domain(struct scpsys_domain *pd)
53562306a36Sopenharmony_ci{
53662306a36Sopenharmony_ci	int ret;
53762306a36Sopenharmony_ci
53862306a36Sopenharmony_ci	/*
53962306a36Sopenharmony_ci	 * We're in the error cleanup already, so we only complain,
54062306a36Sopenharmony_ci	 * but won't emit another error on top of the original one.
54162306a36Sopenharmony_ci	 */
54262306a36Sopenharmony_ci	ret = pm_genpd_remove(&pd->genpd);
54362306a36Sopenharmony_ci	if (ret < 0)
54462306a36Sopenharmony_ci		dev_err(pd->scpsys->dev,
54562306a36Sopenharmony_ci			"failed to remove domain '%s' : %d - state may be inconsistent\n",
54662306a36Sopenharmony_ci			pd->genpd.name, ret);
54762306a36Sopenharmony_ci	if (scpsys_domain_is_on(pd))
54862306a36Sopenharmony_ci		scpsys_power_off(&pd->genpd);
54962306a36Sopenharmony_ci
55062306a36Sopenharmony_ci	clk_bulk_put(pd->num_clks, pd->clks);
55162306a36Sopenharmony_ci	clk_bulk_put(pd->num_subsys_clks, pd->subsys_clks);
55262306a36Sopenharmony_ci}
55362306a36Sopenharmony_ci
55462306a36Sopenharmony_cistatic void scpsys_domain_cleanup(struct scpsys *scpsys)
55562306a36Sopenharmony_ci{
55662306a36Sopenharmony_ci	struct generic_pm_domain *genpd;
55762306a36Sopenharmony_ci	struct scpsys_domain *pd;
55862306a36Sopenharmony_ci	int i;
55962306a36Sopenharmony_ci
56062306a36Sopenharmony_ci	for (i = scpsys->pd_data.num_domains - 1; i >= 0; i--) {
56162306a36Sopenharmony_ci		genpd = scpsys->pd_data.domains[i];
56262306a36Sopenharmony_ci		if (genpd) {
56362306a36Sopenharmony_ci			pd = to_scpsys_domain(genpd);
56462306a36Sopenharmony_ci			scpsys_remove_one_domain(pd);
56562306a36Sopenharmony_ci		}
56662306a36Sopenharmony_ci	}
56762306a36Sopenharmony_ci}
56862306a36Sopenharmony_ci
56962306a36Sopenharmony_cistatic const struct of_device_id scpsys_of_match[] = {
57062306a36Sopenharmony_ci	{
57162306a36Sopenharmony_ci		.compatible = "mediatek,mt6795-power-controller",
57262306a36Sopenharmony_ci		.data = &mt6795_scpsys_data,
57362306a36Sopenharmony_ci	},
57462306a36Sopenharmony_ci	{
57562306a36Sopenharmony_ci		.compatible = "mediatek,mt8167-power-controller",
57662306a36Sopenharmony_ci		.data = &mt8167_scpsys_data,
57762306a36Sopenharmony_ci	},
57862306a36Sopenharmony_ci	{
57962306a36Sopenharmony_ci		.compatible = "mediatek,mt8173-power-controller",
58062306a36Sopenharmony_ci		.data = &mt8173_scpsys_data,
58162306a36Sopenharmony_ci	},
58262306a36Sopenharmony_ci	{
58362306a36Sopenharmony_ci		.compatible = "mediatek,mt8183-power-controller",
58462306a36Sopenharmony_ci		.data = &mt8183_scpsys_data,
58562306a36Sopenharmony_ci	},
58662306a36Sopenharmony_ci	{
58762306a36Sopenharmony_ci		.compatible = "mediatek,mt8186-power-controller",
58862306a36Sopenharmony_ci		.data = &mt8186_scpsys_data,
58962306a36Sopenharmony_ci	},
59062306a36Sopenharmony_ci	{
59162306a36Sopenharmony_ci		.compatible = "mediatek,mt8188-power-controller",
59262306a36Sopenharmony_ci		.data = &mt8188_scpsys_data,
59362306a36Sopenharmony_ci	},
59462306a36Sopenharmony_ci	{
59562306a36Sopenharmony_ci		.compatible = "mediatek,mt8192-power-controller",
59662306a36Sopenharmony_ci		.data = &mt8192_scpsys_data,
59762306a36Sopenharmony_ci	},
59862306a36Sopenharmony_ci	{
59962306a36Sopenharmony_ci		.compatible = "mediatek,mt8195-power-controller",
60062306a36Sopenharmony_ci		.data = &mt8195_scpsys_data,
60162306a36Sopenharmony_ci	},
60262306a36Sopenharmony_ci	{ }
60362306a36Sopenharmony_ci};
60462306a36Sopenharmony_ci
60562306a36Sopenharmony_cistatic int scpsys_probe(struct platform_device *pdev)
60662306a36Sopenharmony_ci{
60762306a36Sopenharmony_ci	struct device *dev = &pdev->dev;
60862306a36Sopenharmony_ci	struct device_node *np = dev->of_node;
60962306a36Sopenharmony_ci	const struct scpsys_soc_data *soc;
61062306a36Sopenharmony_ci	struct device_node *node;
61162306a36Sopenharmony_ci	struct device *parent;
61262306a36Sopenharmony_ci	struct scpsys *scpsys;
61362306a36Sopenharmony_ci	int ret;
61462306a36Sopenharmony_ci
61562306a36Sopenharmony_ci	soc = of_device_get_match_data(&pdev->dev);
61662306a36Sopenharmony_ci	if (!soc) {
61762306a36Sopenharmony_ci		dev_err(&pdev->dev, "no power controller data\n");
61862306a36Sopenharmony_ci		return -EINVAL;
61962306a36Sopenharmony_ci	}
62062306a36Sopenharmony_ci
62162306a36Sopenharmony_ci	scpsys = devm_kzalloc(dev, struct_size(scpsys, domains, soc->num_domains), GFP_KERNEL);
62262306a36Sopenharmony_ci	if (!scpsys)
62362306a36Sopenharmony_ci		return -ENOMEM;
62462306a36Sopenharmony_ci
62562306a36Sopenharmony_ci	scpsys->dev = dev;
62662306a36Sopenharmony_ci	scpsys->soc_data = soc;
62762306a36Sopenharmony_ci
62862306a36Sopenharmony_ci	scpsys->pd_data.domains = scpsys->domains;
62962306a36Sopenharmony_ci	scpsys->pd_data.num_domains = soc->num_domains;
63062306a36Sopenharmony_ci
63162306a36Sopenharmony_ci	parent = dev->parent;
63262306a36Sopenharmony_ci	if (!parent) {
63362306a36Sopenharmony_ci		dev_err(dev, "no parent for syscon devices\n");
63462306a36Sopenharmony_ci		return -ENODEV;
63562306a36Sopenharmony_ci	}
63662306a36Sopenharmony_ci
63762306a36Sopenharmony_ci	scpsys->base = syscon_node_to_regmap(parent->of_node);
63862306a36Sopenharmony_ci	if (IS_ERR(scpsys->base)) {
63962306a36Sopenharmony_ci		dev_err(dev, "no regmap available\n");
64062306a36Sopenharmony_ci		return PTR_ERR(scpsys->base);
64162306a36Sopenharmony_ci	}
64262306a36Sopenharmony_ci
64362306a36Sopenharmony_ci	ret = -ENODEV;
64462306a36Sopenharmony_ci	for_each_available_child_of_node(np, node) {
64562306a36Sopenharmony_ci		struct generic_pm_domain *domain;
64662306a36Sopenharmony_ci
64762306a36Sopenharmony_ci		domain = scpsys_add_one_domain(scpsys, node);
64862306a36Sopenharmony_ci		if (IS_ERR(domain)) {
64962306a36Sopenharmony_ci			ret = PTR_ERR(domain);
65062306a36Sopenharmony_ci			of_node_put(node);
65162306a36Sopenharmony_ci			goto err_cleanup_domains;
65262306a36Sopenharmony_ci		}
65362306a36Sopenharmony_ci
65462306a36Sopenharmony_ci		ret = scpsys_add_subdomain(scpsys, node);
65562306a36Sopenharmony_ci		if (ret) {
65662306a36Sopenharmony_ci			of_node_put(node);
65762306a36Sopenharmony_ci			goto err_cleanup_domains;
65862306a36Sopenharmony_ci		}
65962306a36Sopenharmony_ci	}
66062306a36Sopenharmony_ci
66162306a36Sopenharmony_ci	if (ret) {
66262306a36Sopenharmony_ci		dev_dbg(dev, "no power domains present\n");
66362306a36Sopenharmony_ci		return ret;
66462306a36Sopenharmony_ci	}
66562306a36Sopenharmony_ci
66662306a36Sopenharmony_ci	ret = of_genpd_add_provider_onecell(np, &scpsys->pd_data);
66762306a36Sopenharmony_ci	if (ret) {
66862306a36Sopenharmony_ci		dev_err(dev, "failed to add provider: %d\n", ret);
66962306a36Sopenharmony_ci		goto err_cleanup_domains;
67062306a36Sopenharmony_ci	}
67162306a36Sopenharmony_ci
67262306a36Sopenharmony_ci	return 0;
67362306a36Sopenharmony_ci
67462306a36Sopenharmony_cierr_cleanup_domains:
67562306a36Sopenharmony_ci	scpsys_domain_cleanup(scpsys);
67662306a36Sopenharmony_ci	return ret;
67762306a36Sopenharmony_ci}
67862306a36Sopenharmony_ci
67962306a36Sopenharmony_cistatic struct platform_driver scpsys_pm_domain_driver = {
68062306a36Sopenharmony_ci	.probe = scpsys_probe,
68162306a36Sopenharmony_ci	.driver = {
68262306a36Sopenharmony_ci		.name = "mtk-power-controller",
68362306a36Sopenharmony_ci		.suppress_bind_attrs = true,
68462306a36Sopenharmony_ci		.of_match_table = scpsys_of_match,
68562306a36Sopenharmony_ci	},
68662306a36Sopenharmony_ci};
68762306a36Sopenharmony_cibuiltin_platform_driver(scpsys_pm_domain_driver);
688