+};
+
+struct scpsys_soc_data {
+ const struct scpsys_domain_data *domains;
+ int num_domains;
+ int pwr_sta_offs;
+ int pwr_sta2nd_offs;
+};
+
+struct scpsys {
+ struct device *dev;
+ void __iomem *base;
+ const struct scpsys_soc_data *soc_data;
+ struct genpd_onecell_data pd_data;
+ struct generic_pm_domain *domains[];
+};
+
+#define to_scpsys_domain(gpd) container_of(gpd, struct scpsys_domain, genpd)
+
+static int scpsys_domain_is_on(struct scpsys_domain *pd)
+{
+ struct scpsys *scpsys = pd->scpsys;
+
+ u32 status = readl(scpsys->base + scpsys->soc_data->pwr_sta_offs) &
pd->data->sta_mask;
+ u32 status2 = readl(scpsys->base + scpsys->soc_data->pwr_sta2nd_offs) &
pd->data->sta_mask;
+
+ /*
+ * A domain is on when both status bits are set. If only one is set
+ * return an error. This happens while powering up a domain
+ */
+
+ if (status && status2)
+ return true;
+ if (!status && !status2)
+ return false;
+
+ return -EINVAL;
+}
+
+static int scpsys_sram_enable(struct scpsys_domain *pd, void __iomem *ctl_addr)
+{
+ u32 pdn_ack = pd->data->sram_pdn_ack_bits;
+ u32 val;
+ int tmp;
+ int ret;
+
+ val = readl(ctl_addr);
+ val &= ~pd->data->sram_pdn_bits;
+ writel(val, ctl_addr);
+
+ /* Either wait until SRAM_PDN_ACK all 1 or 0 */
+ ret = readl_poll_timeout(ctl_addr, tmp, (tmp & pdn_ack) == 0,
MTK_POLL_DELAY_US,
+ MTK_POLL_TIMEOUT);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int scpsys_sram_disable(struct scpsys_domain *pd, void __iomem
*ctl_addr)
+{
+ u32 pdn_ack = pd->data->sram_pdn_ack_bits;
+ u32 val;
+ int tmp;
+
+ val = readl(ctl_addr);
+ val |= pd->data->sram_pdn_bits;
+ writel(val, ctl_addr);
+
+ /* Either wait until SRAM_PDN_ACK all 1 or 0 */
+ return readl_poll_timeout(ctl_addr, tmp, (tmp & pdn_ack) == pdn_ack,
MTK_POLL_DELAY_US,
+ MTK_POLL_TIMEOUT);
+}
+
+static int scpsys_bus_protect_enable(struct scpsys_domain *pd)
+{
+ const struct scpsys_bus_prot_data *bp_data = &pd->data->bp_infracfg;
+
+ if (!bp_data->bus_prot_mask)
+ return 0;
+
+ return mtk_infracfg_set_bus_protection(pd->infracfg,
bp_data->bus_prot_mask,
+ bp_data->bus_prot_reg_update);
+}
+
+static int scpsys_bus_protect_disable(struct scpsys_domain *pd)
+{
+ const struct scpsys_bus_prot_data *bp_data = &pd->data->bp_infracfg;
+
+ if (!bp_data->bus_prot_mask)
+ return 0;
+
+ return mtk_infracfg_clear_bus_protection(pd->infracfg,
bp_data->bus_prot_mask,
+ bp_data->bus_prot_reg_update);
+}
+
+static int scpsys_power_on(struct generic_pm_domain *genpd)
+{
+ struct scpsys_domain *pd = container_of(genpd, struct scpsys_domain,
genpd);
+ struct scpsys *scpsys = pd->scpsys;
+ void __iomem *ctl_addr = scpsys->base + pd->data->ctl_offs;
+ int ret, tmp;
+ u32 val;
+
+ ret = clk_bulk_enable(pd->num_clks, pd->clks);
+ if (ret)
+ return ret;
+
+ /* subsys power on */
+ val = readl(ctl_addr);
+ val |= PWR_ON_BIT;
+ writel(val, ctl_addr);
+ val |= PWR_ON_2ND_BIT;
+ writel(val, ctl_addr);
+
+ /* wait until PWR_ACK = 1 */
+ ret = readx_poll_timeout(scpsys_domain_is_on, pd, tmp, tmp > 0,
MTK_POLL_DELAY_US,
+ MTK_POLL_TIMEOUT);
+ if (ret < 0)
+ goto err_pwr_ack;
+
+ val &= ~PWR_CLK_DIS_BIT;
+ writel(val, ctl_addr);
+
+ val &= ~PWR_ISO_BIT;
+ writel(val, ctl_addr);
+
+ val |= PWR_RST_B_BIT;
+ writel(val, ctl_addr);
+
+ ret = scpsys_sram_enable(pd, ctl_addr);
+ if (ret < 0)
+ goto err_pwr_ack;
+
+ ret = scpsys_bus_protect_disable(pd);
+ if (ret < 0)
+ goto err_pwr_ack;
+
+ return 0;
+
+err_pwr_ack:
+ clk_bulk_disable(pd->num_clks, pd->clks);
+ dev_err(scpsys->dev, "Failed to power on domain %s\n", genpd->name);
+
+ return ret;
+}
+
+static int scpsys_power_off(struct generic_pm_domain *genpd)
+{
+ struct scpsys_domain *pd = container_of(genpd, struct scpsys_domain,
genpd);
+ struct scpsys *scpsys = pd->scpsys;
+ void __iomem *ctl_addr = scpsys->base + pd->data->ctl_offs;
+ int ret, tmp;
+ u32 val;
+
+ ret = scpsys_bus_protect_enable(pd);
+ if (ret < 0)
+ return ret;
+
+ ret = scpsys_sram_disable(pd, ctl_addr);
+ if (ret < 0)
+ return ret;
+
+ /* subsys power off */
+ val = readl(ctl_addr);
+ val |= PWR_ISO_BIT;
+ writel(val, ctl_addr);
+
+ val &= ~PWR_RST_B_BIT;
+ writel(val, ctl_addr);
+
+ val |= PWR_CLK_DIS_BIT;
+ writel(val, ctl_addr);
+
+ val &= ~PWR_ON_BIT;
+ writel(val, ctl_addr);
+
+ val &= ~PWR_ON_2ND_BIT;
+ writel(val, ctl_addr);
+
+ /* wait until PWR_ACK = 0 */
+ ret = readx_poll_timeout(scpsys_domain_is_on, pd, tmp, tmp == 0,
MTK_POLL_DELAY_US,
+ MTK_POLL_TIMEOUT);
+ if (ret < 0)
+ return ret;
+
+ clk_bulk_disable(pd->num_clks, pd->clks);
+
+ return 0;
+}
+
+static int scpsys_add_one_domain(struct scpsys *scpsys, struct device_node
*node)
+{
+ const struct scpsys_domain_data *domain_data;
+ struct scpsys_domain *pd;
+ int i, ret;
+ u32 id;
+
+ ret = of_property_read_u32(node, "reg", &id);
+ if (ret) {
+ dev_err(scpsys->dev, "%pOFn: failed to retrieve domain id from reg:
%d\n", node,
+ ret);
+ return -EINVAL;
+ }
+
+ if (id >= scpsys->soc_data->num_domains) {
+ dev_err(scpsys->dev, "%pOFn: invalid domain id %d\n", node, id);
+ return -EINVAL;
+ }
+
+ domain_data = &scpsys->soc_data->domains[id];
+ if (!domain_data) {
+ dev_err(scpsys->dev, "%pOFn: undefined domain id %d\n", node,
id);
+ return -EINVAL;
+ }
+
+ pd = devm_kzalloc(scpsys->dev, sizeof(*pd), GFP_KERNEL);
+ if (!pd)
+ return -ENOMEM;
+
+ pd->data = domain_data;
+ pd->scpsys = scpsys;
+
+ pd->infracfg = syscon_regmap_lookup_by_phandle(node,
"mediatek,infracfg");
+ if (IS_ERR(pd->infracfg))
+ pd->infracfg = NULL;
+
+ pd->num_clks = of_clk_get_parent_count(node);
+ if (pd->num_clks > 0) {
+ pd->clks = devm_kcalloc(scpsys->dev, pd->num_clks,
sizeof(*pd->clks), GFP_KERNEL);
+ if (!pd->clks)
+ return -ENOMEM;
+ } else {
+ pd->num_clks = 0;
+ }
+
+ for (i = 0; i < pd->num_clks; i++) {
+ pd->clks[i].clk = of_clk_get(node, i);
+ if (IS_ERR(pd->clks[i].clk)) {
+ ret = PTR_ERR(pd->clks[i].clk);
+ dev_err(scpsys->dev, "%pOFn: failed to get clk at index %d:
%d\n", node, i,
+ ret);
+ return ret;
+ }
+ }
+
+ ret = clk_bulk_prepare(pd->num_clks, pd->clks);
+ if (ret)
+ goto err_put_clocks;
+
+ /*
+ * Initially turn on all domains to make the domains usable
+ * with !CONFIG_PM and to get the hardware in sync with the
+ * software. The unused domains will be switched off during
+ * late_init time.
+ */
+ ret = scpsys_power_on(&pd->genpd);
+ if (ret < 0) {
+ dev_err(scpsys->dev, "failed to power on domain %pOFN with error
%d\n", node, ret);
+ goto err_unprepare_clocks;
+ }
+
+ pd->genpd.name = node->name;
+ pd->genpd.power_off = scpsys_power_off;
+ pd->genpd.power_on = scpsys_power_on;
+
+ pm_genpd_init(&pd->genpd, NULL, false);
+
+ scpsys->domains[id] = &pd->genpd;
+ return 0;
+
+err_unprepare_clocks:
+ clk_bulk_unprepare(pd->num_clks, pd->clks);
+err_put_clocks:
+ clk_bulk_put(pd->num_clks, pd->clks);
+ devm_kfree(scpsys->dev, pd->clks);
+ pd->num_clks = 0;
+ return ret;
+}
+
+static int scpsys_add_subdomain(struct scpsys *scpsys, struct device_node
*parent)
+{
+ struct device_node *child;
+ struct generic_pm_domain *child_pd, *parent_pd;
+ int ret;
+
+ for_each_child_of_node(parent, child) {
+ u32 id;
+
+ ret = of_property_read_u32(parent, "reg", &id);
+ if (ret) {
+ dev_err(scpsys->dev, "%pOFn: failed to get parent domain id:
%d\n", child,
+ ret);
+ goto err_put_node;
+ }
+ parent_pd = scpsys->pd_data.domains[id];
+