| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * Copyright (C) 2017 ZTE Ltd. |
| * |
| * Author: Baoyou Xie <baoyou.xie@linaro.org> |
| */ |
| |
| #include <linux/delay.h> |
| #include <linux/err.h> |
| #include <linux/io.h> |
| #include <linux/of.h> |
| |
| #include "zx2967_pm_domains.h" |
| |
| #define PCU_DM_CLKEN(zpd) ((zpd)->reg_offset[REG_CLKEN]) |
| #define PCU_DM_ISOEN(zpd) ((zpd)->reg_offset[REG_ISOEN]) |
| #define PCU_DM_RSTEN(zpd) ((zpd)->reg_offset[REG_RSTEN]) |
| #define PCU_DM_PWREN(zpd) ((zpd)->reg_offset[REG_PWREN]) |
| #define PCU_DM_ACK_SYNC(zpd) ((zpd)->reg_offset[REG_ACK_SYNC]) |
| |
| static void __iomem *pcubase; |
| |
| static int zx2967_power_on(struct generic_pm_domain *domain) |
| { |
| struct zx2967_pm_domain *zpd = (struct zx2967_pm_domain *)domain; |
| unsigned long loop = 1000; |
| u32 val; |
| |
| val = readl_relaxed(pcubase + PCU_DM_PWREN(zpd)); |
| if (zpd->polarity == PWREN) |
| val |= BIT(zpd->bit); |
| else |
| val &= ~BIT(zpd->bit); |
| writel_relaxed(val, pcubase + PCU_DM_PWREN(zpd)); |
| |
| do { |
| udelay(1); |
| val = readl_relaxed(pcubase + PCU_DM_ACK_SYNC(zpd)) |
| & BIT(zpd->bit); |
| } while (--loop && !val); |
| |
| if (!loop) { |
| pr_err("Error: %s %s fail\n", __func__, domain->name); |
| return -EIO; |
| } |
| |
| val = readl_relaxed(pcubase + PCU_DM_RSTEN(zpd)); |
| val |= BIT(zpd->bit); |
| writel_relaxed(val, pcubase + PCU_DM_RSTEN(zpd)); |
| udelay(5); |
| |
| val = readl_relaxed(pcubase + PCU_DM_ISOEN(zpd)); |
| val &= ~BIT(zpd->bit); |
| writel_relaxed(val, pcubase + PCU_DM_ISOEN(zpd)); |
| udelay(5); |
| |
| val = readl_relaxed(pcubase + PCU_DM_CLKEN(zpd)); |
| val |= BIT(zpd->bit); |
| writel_relaxed(val, pcubase + PCU_DM_CLKEN(zpd)); |
| udelay(5); |
| |
| pr_debug("poweron %s\n", domain->name); |
| |
| return 0; |
| } |
| |
| static int zx2967_power_off(struct generic_pm_domain *domain) |
| { |
| struct zx2967_pm_domain *zpd = (struct zx2967_pm_domain *)domain; |
| unsigned long loop = 1000; |
| u32 val; |
| |
| val = readl_relaxed(pcubase + PCU_DM_CLKEN(zpd)); |
| val &= ~BIT(zpd->bit); |
| writel_relaxed(val, pcubase + PCU_DM_CLKEN(zpd)); |
| udelay(5); |
| |
| val = readl_relaxed(pcubase + PCU_DM_ISOEN(zpd)); |
| val |= BIT(zpd->bit); |
| writel_relaxed(val, pcubase + PCU_DM_ISOEN(zpd)); |
| udelay(5); |
| |
| val = readl_relaxed(pcubase + PCU_DM_RSTEN(zpd)); |
| val &= ~BIT(zpd->bit); |
| writel_relaxed(val, pcubase + PCU_DM_RSTEN(zpd)); |
| udelay(5); |
| |
| val = readl_relaxed(pcubase + PCU_DM_PWREN(zpd)); |
| if (zpd->polarity == PWREN) |
| val &= ~BIT(zpd->bit); |
| else |
| val |= BIT(zpd->bit); |
| writel_relaxed(val, pcubase + PCU_DM_PWREN(zpd)); |
| |
| do { |
| udelay(1); |
| val = readl_relaxed(pcubase + PCU_DM_ACK_SYNC(zpd)) |
| & BIT(zpd->bit); |
| } while (--loop && val); |
| |
| if (!loop) { |
| pr_err("Error: %s %s fail\n", __func__, domain->name); |
| return -EIO; |
| } |
| |
| pr_debug("poweroff %s\n", domain->name); |
| |
| return 0; |
| } |
| |
| int zx2967_pd_probe(struct platform_device *pdev, |
| struct generic_pm_domain **zx_pm_domains, |
| int domain_num) |
| { |
| struct genpd_onecell_data *genpd_data; |
| struct resource *res; |
| int i; |
| |
| genpd_data = devm_kzalloc(&pdev->dev, sizeof(*genpd_data), GFP_KERNEL); |
| if (!genpd_data) |
| return -ENOMEM; |
| |
| genpd_data->domains = zx_pm_domains; |
| genpd_data->num_domains = domain_num; |
| |
| res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
| pcubase = devm_ioremap_resource(&pdev->dev, res); |
| if (IS_ERR(pcubase)) |
| return PTR_ERR(pcubase); |
| |
| for (i = 0; i < domain_num; ++i) { |
| zx_pm_domains[i]->power_on = zx2967_power_on; |
| zx_pm_domains[i]->power_off = zx2967_power_off; |
| |
| pm_genpd_init(zx_pm_domains[i], NULL, false); |
| } |
| |
| of_genpd_add_provider_onecell(pdev->dev.of_node, genpd_data); |
| dev_info(&pdev->dev, "powerdomain init ok\n"); |
| return 0; |
| } |