| // SPDX-License-Identifier: GPL-2.0-or-later |
| /* |
| * MMP PMU power island support |
| * |
| * Copyright (C) 2020 Lubomir Rintel <lkundrak@v3.sk> |
| */ |
| |
| #include <linux/pm_domain.h> |
| #include <linux/slab.h> |
| #include <linux/io.h> |
| |
| #include "clk.h" |
| |
| #define to_mmp_pm_domain(genpd) container_of(genpd, struct mmp_pm_domain, genpd) |
| |
| struct mmp_pm_domain { |
| struct generic_pm_domain genpd; |
| void __iomem *reg; |
| spinlock_t *lock; |
| u32 power_on; |
| u32 reset; |
| u32 clock_enable; |
| unsigned int flags; |
| }; |
| |
| static int mmp_pm_domain_power_on(struct generic_pm_domain *genpd) |
| { |
| struct mmp_pm_domain *pm_domain = to_mmp_pm_domain(genpd); |
| unsigned long flags = 0; |
| u32 val; |
| |
| if (pm_domain->lock) |
| spin_lock_irqsave(pm_domain->lock, flags); |
| |
| val = readl(pm_domain->reg); |
| |
| /* Turn on the power island */ |
| val |= pm_domain->power_on; |
| writel(val, pm_domain->reg); |
| |
| /* Disable isolation */ |
| val |= 0x100; |
| writel(val, pm_domain->reg); |
| |
| /* Some blocks need to be reset after a power up */ |
| if (pm_domain->reset || pm_domain->clock_enable) { |
| u32 after_power_on = val; |
| |
| val &= ~pm_domain->reset; |
| writel(val, pm_domain->reg); |
| |
| val |= pm_domain->clock_enable; |
| writel(val, pm_domain->reg); |
| |
| val |= pm_domain->reset; |
| writel(val, pm_domain->reg); |
| |
| writel(after_power_on, pm_domain->reg); |
| } |
| |
| if (pm_domain->lock) |
| spin_unlock_irqrestore(pm_domain->lock, flags); |
| |
| return 0; |
| } |
| |
| static int mmp_pm_domain_power_off(struct generic_pm_domain *genpd) |
| { |
| struct mmp_pm_domain *pm_domain = to_mmp_pm_domain(genpd); |
| unsigned long flags = 0; |
| u32 val; |
| |
| if (pm_domain->flags & MMP_PM_DOMAIN_NO_DISABLE) |
| return 0; |
| |
| if (pm_domain->lock) |
| spin_lock_irqsave(pm_domain->lock, flags); |
| |
| /* Turn off and isolate the power island. */ |
| val = readl(pm_domain->reg); |
| val &= ~pm_domain->power_on; |
| val &= ~0x100; |
| writel(val, pm_domain->reg); |
| |
| if (pm_domain->lock) |
| spin_unlock_irqrestore(pm_domain->lock, flags); |
| |
| return 0; |
| } |
| |
| struct generic_pm_domain *mmp_pm_domain_register(const char *name, |
| void __iomem *reg, |
| u32 power_on, u32 reset, u32 clock_enable, |
| unsigned int flags, spinlock_t *lock) |
| { |
| struct mmp_pm_domain *pm_domain; |
| |
| pm_domain = kzalloc(sizeof(*pm_domain), GFP_KERNEL); |
| if (!pm_domain) |
| return ERR_PTR(-ENOMEM); |
| |
| pm_domain->reg = reg; |
| pm_domain->power_on = power_on; |
| pm_domain->reset = reset; |
| pm_domain->clock_enable = clock_enable; |
| pm_domain->flags = flags; |
| pm_domain->lock = lock; |
| |
| pm_genpd_init(&pm_domain->genpd, NULL, true); |
| pm_domain->genpd.name = name; |
| pm_domain->genpd.power_on = mmp_pm_domain_power_on; |
| pm_domain->genpd.power_off = mmp_pm_domain_power_off; |
| |
| return &pm_domain->genpd; |
| } |