diff mbox

[5/9] ARM: dove: create a proper PMU driver for power domains, PMU IRQs and resets

Message ID E1YW7t5-0003mI-Sk@rmk-PC.arm.linux.org.uk (mailing list archive)
State New, archived
Headers show

Commit Message

Russell King March 12, 2015, 6:31 p.m. UTC
The PMU device contains an interrupt controller, power control and
resets.  The interrupt controller is a little sub-standard in that
there is no race free way to clear down pending interrupts, so we try
to avoid problems by reducing the window as much as possible, and
clearing as infrequently as possible.

The interrupt support is implemented using an IRQ domain, and the
parent interrupt referenced in the standard DT way.

The power domains and reset support is closely related - there is a
defined sequence for powering down a domain which is tightly coupled
with asserting the reset.  Hence, it makes sense to group these two
together.

This patch adds the core PMU driver: power domains must be defined in
the DT file in order to make use of them.  The reset controller can
be referenced in the standard way for reset controllers.

Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
---
 arch/arm/mach-mvebu/Kconfig  |   1 +
 drivers/soc/Makefile         |   1 +
 drivers/soc/dove/Makefile    |   1 +
 drivers/soc/dove/pmu.c       | 399 +++++++++++++++++++++++++++++++++++++++++++
 include/linux/soc/dove/pmu.h |   6 +
 5 files changed, 408 insertions(+)
 create mode 100644 drivers/soc/dove/Makefile
 create mode 100644 drivers/soc/dove/pmu.c
 create mode 100644 include/linux/soc/dove/pmu.h

Comments

Arnd Bergmann March 13, 2015, 12:07 p.m. UTC | #1
On Thursday 12 March 2015 18:31:15 Russell King wrote:
> +int __init dove_init_pmu(void)
> +{
> +       struct device_node *np_pmu, *np;
> +       struct pmu_data *pmu;
> +       int ret, parent_irq;
> +
> +       /* Lookup the PMU node */
> +       np_pmu = of_find_compatible_node(NULL, NULL, "marvell,dove-pmu");
> +       if (!np_pmu)
> +               return 0;

What is the reason that this is not a platform_driver? I think you
should try to make it one, or explain in the changelog the reason
for not making it one. This obviously ties in with the question I
asked about who calls dove_init_pmu().

> +               ret = of_parse_phandle_with_args(np, "resets", "#reset-cells",
> +                                                0, &args);
> +               if (ret == 0) {
> +                       if (args.np == pmu->of_node)
> +                               domain->rst_mask = BIT(args.args[0]);
> +                       of_node_put(args.np);
> +               }

In particular, manually parsing the "resets" property is something
we should try to avoid. With a platform driver, this could become
devm_reset_control_get(), otherwise I think of_reset_control_get()
would also do the right thing here.

	Arnd
Russell King - ARM Linux March 13, 2015, 12:29 p.m. UTC | #2
On Fri, Mar 13, 2015 at 01:07:06PM +0100, Arnd Bergmann wrote:
> On Thursday 12 March 2015 18:31:15 Russell King wrote:
> > +int __init dove_init_pmu(void)
> > +{
> > +       struct device_node *np_pmu, *np;
> > +       struct pmu_data *pmu;
> > +       int ret, parent_irq;
> > +
> > +       /* Lookup the PMU node */
> > +       np_pmu = of_find_compatible_node(NULL, NULL, "marvell,dove-pmu");
> > +       if (!np_pmu)
> > +               return 0;
> 
> What is the reason that this is not a platform_driver? I think you
> should try to make it one, or explain in the changelog the reason
> for not making it one. This obviously ties in with the question I
> asked about who calls dove_init_pmu().

It's because I need for the PM domain support to be available early
right now, and much of this code pre-dates the integration of OF with
PM domains.  Even booting with DT, I still need some platform devices
manually declared and bound to these PM domains in order to (a) properly
test this code, (b) test other parts of the system effectively, and
(c) have a working system.

However, I do agree that in the longer term this should probably be
converted to a platform device driver, assuming that everything that
makes use of it copes with it.

However, one thing that is a concern with that is the PM domain code
does not yet support the removal of PM domains.  I guess we should
ensure that PM domain providers set driver.suppress_bind_attrs so
that at least userspace can't unbind these drivers.

> > +               ret = of_parse_phandle_with_args(np, "resets", "#reset-cells",
> > +                                                0, &args);
> > +               if (ret == 0) {
> > +                       if (args.np == pmu->of_node)
> > +                               domain->rst_mask = BIT(args.args[0]);
> > +                       of_node_put(args.np);
> > +               }
> 
> In particular, manually parsing the "resets" property is something
> we should try to avoid. With a platform driver, this could become
> devm_reset_control_get(), otherwise I think of_reset_control_get()
> would also do the right thing here.

The hardware requires a specific sequence of register writes for the
PM domain code, which includes the reset register.

The problem is that if we were to use the reset API directly from the
PM domain code, we would have to separate the locks for the reset code
from the PM domain code.  That then leads to there being a race between
the reset code potentially being able to write to the reset register in
the middle of a PM domain sequence.
Arnd Bergmann March 13, 2015, 12:42 p.m. UTC | #3
On Friday 13 March 2015 12:29:44 Russell King - ARM Linux wrote:
> On Fri, Mar 13, 2015 at 01:07:06PM +0100, Arnd Bergmann wrote:
> > On Thursday 12 March 2015 18:31:15 Russell King wrote:
> > > +int __init dove_init_pmu(void)
> > > +{
> > > +       struct device_node *np_pmu, *np;
> > > +       struct pmu_data *pmu;
> > > +       int ret, parent_irq;
> > > +
> > > +       /* Lookup the PMU node */
> > > +       np_pmu = of_find_compatible_node(NULL, NULL, "marvell,dove-pmu");
> > > +       if (!np_pmu)
> > > +               return 0;
> > 
> > What is the reason that this is not a platform_driver? I think you
> > should try to make it one, or explain in the changelog the reason
> > for not making it one. This obviously ties in with the question I
> > asked about who calls dove_init_pmu().
> 
> It's because I need for the PM domain support to be available early
> right now, and much of this code pre-dates the integration of OF with
> PM domains.  Even booting with DT, I still need some platform devices
> manually declared and bound to these PM domains in order to (a) properly
> test this code, (b) test other parts of the system effectively, and
> (c) have a working system.
> 
> However, I do agree that in the longer term this should probably be
> converted to a platform device driver, assuming that everything that
> makes use of it copes with it.

Ok. Given that the device is (also) an irqchip, would it work to use
IRQCHIP_DECLARE() to have the driver initialized at init_irq() time
as an alternative, or is that too early for some of the things this
driver does?

> The hardware requires a specific sequence of register writes for the
> PM domain code, which includes the reset register.
> 
> The problem is that if we were to use the reset API directly from the
> PM domain code, we would have to separate the locks for the reset code
> from the PM domain code.  That then leads to there being a race between
> the reset code potentially being able to write to the reset register in
> the middle of a PM domain sequence.

I see. I may be missing something here, but I think you could still
use of_reset_controller_get() once the reset controller is enabled
and then manually access the register from the PM domain code while
holding the pmu lock. You wouldn't be able to use device_reset()
or similar as you explain, but you could avoid parsing the DT manually.

	Arnd
Russell King - ARM Linux March 13, 2015, 12:47 p.m. UTC | #4
On Fri, Mar 13, 2015 at 01:42:35PM +0100, Arnd Bergmann wrote:
> On Friday 13 March 2015 12:29:44 Russell King - ARM Linux wrote:
> > The hardware requires a specific sequence of register writes for the
> > PM domain code, which includes the reset register.
> > 
> > The problem is that if we were to use the reset API directly from the
> > PM domain code, we would have to separate the locks for the reset code
> > from the PM domain code.  That then leads to there being a race between
> > the reset code potentially being able to write to the reset register in
> > the middle of a PM domain sequence.
> 
> I see. I may be missing something here, but I think you could still
> use of_reset_controller_get() once the reset controller is enabled
> and then manually access the register from the PM domain code while
> holding the pmu lock. You wouldn't be able to use device_reset()
> or similar as you explain, but you could avoid parsing the DT manually.

What about the case when CONFIG_RESET_CONTROLLER is disabled?  In that
case, I would then need to manually parse this anyway.
Russell King - ARM Linux March 13, 2015, 12:59 p.m. UTC | #5
On Fri, Mar 13, 2015 at 01:42:35PM +0100, Arnd Bergmann wrote:
> Ok. Given that the device is (also) an irqchip, would it work to use
> IRQCHIP_DECLARE() to have the driver initialized at init_irq() time
> as an alternative, or is that too early for some of the things this
> driver does?

I've just looked at that - it's not as easy as that, the macro is
defined in drivers/irqchip/irqchip.h which would need a relative
path to pick up - I'm not a fan of relative include paths.
Arnd Bergmann March 13, 2015, 3:08 p.m. UTC | #6
On Friday 13 March 2015 12:47:48 Russell King - ARM Linux wrote:
> On Fri, Mar 13, 2015 at 01:42:35PM +0100, Arnd Bergmann wrote:
> > On Friday 13 March 2015 12:29:44 Russell King - ARM Linux wrote:
> > > The hardware requires a specific sequence of register writes for the
> > > PM domain code, which includes the reset register.
> > > 
> > > The problem is that if we were to use the reset API directly from the
> > > PM domain code, we would have to separate the locks for the reset code
> > > from the PM domain code.  That then leads to there being a race between
> > > the reset code potentially being able to write to the reset register in
> > > the middle of a PM domain sequence.
> > 
> > I see. I may be missing something here, but I think you could still
> > use of_reset_controller_get() once the reset controller is enabled
> > and then manually access the register from the PM domain code while
> > holding the pmu lock. You wouldn't be able to use device_reset()
> > or similar as you explain, but you could avoid parsing the DT manually.
> 
> What about the case when CONFIG_RESET_CONTROLLER is disabled?  In that
> case, I would then need to manually parse this anyway.

I think the easiest and cleanest way would be to let that case not
happen. Adding the reset controller core makes the kernel about 2kb
larger, but in most multiplatform configurations, you get it already.

	Arnd
Russell King - ARM Linux March 13, 2015, 3:28 p.m. UTC | #7
On Fri, Mar 13, 2015 at 04:08:31PM +0100, Arnd Bergmann wrote:
> I think the easiest and cleanest way would be to let that case not
> happen. Adding the reset controller core makes the kernel about 2kb
> larger, but in most multiplatform configurations, you get it already.

Well, I personally hate selecting visible Kconfig symbols.  It's
against my religion.
Arnd Bergmann March 13, 2015, 3:36 p.m. UTC | #8
On Friday 13 March 2015 15:28:43 Russell King - ARM Linux wrote:
> On Fri, Mar 13, 2015 at 04:08:31PM +0100, Arnd Bergmann wrote:
> > I think the easiest and cleanest way would be to let that case not
> > happen. Adding the reset controller core makes the kernel about 2kb
> > larger, but in most multiplatform configurations, you get it already.
> 
> Well, I personally hate selecting visible Kconfig symbols.  It's
> against my religion.
> 

I know what you mean. I already created a patch for this:
 
https://lkml.org/lkml/2015/2/24/317

Need to dig that out again and address the comments I got.

	Arnd
diff mbox

Patch

diff --git a/arch/arm/mach-mvebu/Kconfig b/arch/arm/mach-mvebu/Kconfig
index c1e4567a5ab3..ee69b519b4b1 100644
--- a/arch/arm/mach-mvebu/Kconfig
+++ b/arch/arm/mach-mvebu/Kconfig
@@ -82,6 +82,7 @@  config MACH_DOVE
 	select MACH_MVEBU_ANY
 	select ORION_IRQCHIP
 	select ORION_TIMER
+	select PM_GENERIC_DOMAINS if PM
 	select PINCTRL_DOVE
 	help
 	  Say 'Y' here if you want your kernel to support the
diff --git a/drivers/soc/Makefile b/drivers/soc/Makefile
index 063113d0bd38..7382bfe92743 100644
--- a/drivers/soc/Makefile
+++ b/drivers/soc/Makefile
@@ -2,6 +2,7 @@ 
 # Makefile for the Linux Kernel SOC specific device drivers.
 #
 
+obj-$(CONFIG_MACH_DOVE)		+= dove/
 obj-$(CONFIG_ARCH_QCOM)		+= qcom/
 obj-$(CONFIG_ARCH_TEGRA)	+= tegra/
 obj-$(CONFIG_SOC_TI)		+= ti/
diff --git a/drivers/soc/dove/Makefile b/drivers/soc/dove/Makefile
new file mode 100644
index 000000000000..2db8e65513a3
--- /dev/null
+++ b/drivers/soc/dove/Makefile
@@ -0,0 +1 @@ 
+obj-y		+= pmu.o
diff --git a/drivers/soc/dove/pmu.c b/drivers/soc/dove/pmu.c
new file mode 100644
index 000000000000..41539743ecf9
--- /dev/null
+++ b/drivers/soc/dove/pmu.c
@@ -0,0 +1,399 @@ 
+/*
+ * Marvell Dove PMU support
+ */
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/irqdomain.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>
+#include <linux/of_address.h>
+#include <linux/platform_device.h>
+#include <linux/pm_domain.h>
+#include <linux/reset.h>
+#include <linux/reset-controller.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/soc/dove/pmu.h>
+#include <linux/spinlock.h>
+
+#define NR_PMU_IRQS		7
+
+#define PMC_SW_RST		0x30
+#define PMC_IRQ_CAUSE		0x50
+#define PMC_IRQ_MASK		0x54
+
+#define PMU_PWR			0x10
+#define PMU_ISO			0x58
+
+struct pmu_data {
+	spinlock_t lock;
+	struct device_node *of_node;
+	void __iomem *pmc_base;
+	void __iomem *pmu_base;
+	struct irq_chip_generic *irq_gc;
+	struct irq_domain *irq_domain;
+#ifdef CONFIG_RESET_CONTROLLER
+	struct reset_controller_dev reset;
+#endif
+};
+
+/*
+ * The PMU contains a register to reset various subsystems within the
+ * SoC.  Export this as a reset controller.
+ */
+#ifdef CONFIG_RESET_CONTROLLER
+#define rcdev_to_pmu(rcdev) container_of(rcdev, struct pmu_data, reset)
+
+static int pmu_reset_reset(struct reset_controller_dev *rc, unsigned long id)
+{
+	struct pmu_data *pmu = rcdev_to_pmu(rc);
+	unsigned long flags;
+	u32 val;
+
+	spin_lock_irqsave(&pmu->lock, flags);
+	val = readl_relaxed(pmu->pmc_base + PMC_SW_RST);
+	writel_relaxed(val & ~BIT(id), pmu->pmc_base + PMC_SW_RST);
+	writel_relaxed(val | BIT(id), pmu->pmc_base + PMC_SW_RST);
+	spin_unlock_irqrestore(&pmu->lock, flags);
+
+	return 0;
+}
+
+static int pmu_reset_assert(struct reset_controller_dev *rc, unsigned long id)
+{
+	struct pmu_data *pmu = rcdev_to_pmu(rc);
+	unsigned long flags;
+	u32 val = ~BIT(id);
+
+	spin_lock_irqsave(&pmu->lock, flags);
+	val &= readl_relaxed(pmu->pmc_base + PMC_SW_RST);
+	writel_relaxed(val, pmu->pmc_base + PMC_SW_RST);
+	spin_unlock_irqrestore(&pmu->lock, flags);
+
+	return 0;
+}
+
+static int pmu_reset_deassert(struct reset_controller_dev *rc, unsigned long id)
+{
+	struct pmu_data *pmu = rcdev_to_pmu(rc);
+	unsigned long flags;
+	u32 val = BIT(id);
+
+	spin_lock_irqsave(&pmu->lock, flags);
+	val |= readl_relaxed(pmu->pmc_base + PMC_SW_RST);
+	writel_relaxed(val, pmu->pmc_base + PMC_SW_RST);
+	spin_unlock_irqrestore(&pmu->lock, flags);
+
+	return 0;
+}
+
+static struct reset_control_ops pmu_reset_ops = {
+	.reset = pmu_reset_reset,
+	.assert = pmu_reset_assert,
+	.deassert = pmu_reset_deassert,
+};
+
+static struct reset_controller_dev pmu_reset __initdata = {
+	.ops = &pmu_reset_ops,
+	.owner = THIS_MODULE,
+	.nr_resets = 32,
+};
+
+static void __init pmu_reset_init(struct pmu_data *pmu)
+{
+	int ret;
+
+	pmu->reset = pmu_reset;
+	pmu->reset.of_node = pmu->of_node;
+
+	ret = reset_controller_register(&pmu->reset);
+	if (ret)
+		pr_err("pmu: %s failed: %d\n", "reset_controller_register", ret);
+}
+#else
+static void __init pmu_reset_init(struct pmu_data *pmu)
+{
+}
+#endif
+
+struct pmu_domain {
+	struct pmu_data *pmu;
+	u32 pwr_mask;
+	u32 rst_mask;
+	u32 iso_mask;
+	struct generic_pm_domain base;
+};
+
+#define to_pmu_domain(dom) container_of(dom, struct pmu_domain, base)
+
+/*
+ * This deals with the "old" Marvell sequence of bringing a power domain
+ * down/up, which is: apply power, release reset, disable isolators.
+ *
+ * Later devices apparantly use a different sequence: power up, disable
+ * isolators, assert repair signal, enable SRMA clock, enable AXI clock,
+ * enable module clock, deassert reset.
+ *
+ * Note: reading the assembly, it seems that the IO accessors have an
+ * unfortunate side-effect - they cause memory already read into registers
+ * for the if () to be re-read for the bit-set or bit-clear operation.
+ * The code is written to avoid this.
+ */
+static int pmu_domain_power_off(struct generic_pm_domain *domain)
+{
+	struct pmu_domain *pmu_dom = to_pmu_domain(domain);
+	struct pmu_data *pmu = pmu_dom->pmu;
+	unsigned long flags;
+	unsigned int val;
+	void __iomem *pmu_base = pmu->pmu_base;
+	void __iomem *pmc_base = pmu->pmc_base;
+
+	spin_lock_irqsave(&pmu->lock, flags);
+
+	/* Enable isolators */
+	if (pmu_dom->iso_mask) {
+		val = ~pmu_dom->iso_mask;
+		val &= readl_relaxed(pmu_base + PMU_ISO);
+		writel_relaxed(val, pmu_base + PMU_ISO);
+	}
+
+	/* Reset unit */
+	if (pmu_dom->rst_mask) {
+		val = ~pmu_dom->rst_mask;
+		val &= readl_relaxed(pmc_base + PMC_SW_RST);
+		writel_relaxed(val, pmc_base + PMC_SW_RST);
+	}
+
+	/* Power down */
+	val = readl_relaxed(pmu_base + PMU_PWR) | pmu_dom->pwr_mask;
+	writel_relaxed(val, pmu_base + PMU_PWR);
+
+	spin_unlock_irqrestore(&pmu->lock, flags);
+
+	return 0;
+}
+
+static int pmu_domain_power_on(struct generic_pm_domain *domain)
+{
+	struct pmu_domain *pmu_dom = to_pmu_domain(domain);
+	struct pmu_data *pmu = pmu_dom->pmu;
+	unsigned long flags;
+	unsigned int val;
+	void __iomem *pmu_base = pmu->pmu_base;
+	void __iomem *pmc_base = pmu->pmc_base;
+
+	spin_lock_irqsave(&pmu->lock, flags);
+
+	/* Power on */
+	val = ~pmu_dom->pwr_mask & readl_relaxed(pmu_base + PMU_PWR);
+	writel_relaxed(val, pmu_base + PMU_PWR);
+
+	/* Release reset */
+	if (pmu_dom->rst_mask) {
+		val = pmu_dom->rst_mask;
+		val |= readl_relaxed(pmc_base + PMC_SW_RST);
+		writel_relaxed(val, pmc_base + PMC_SW_RST);
+	}
+
+	/* Disable isolators */
+	if (pmu_dom->iso_mask) {
+		val = pmu_dom->iso_mask;
+		val |= readl_relaxed(pmu_base + PMU_ISO);
+		writel_relaxed(val, pmu_base + PMU_ISO);
+	}
+
+	spin_unlock_irqrestore(&pmu->lock, flags);
+
+	return 0;
+}
+
+static void __pmu_domain_register(struct pmu_domain *domain,
+	struct device_node *np)
+{
+	unsigned int val = readl_relaxed(domain->pmu->pmu_base + PMU_PWR);
+
+	domain->base.power_off = pmu_domain_power_off;
+	domain->base.power_on = pmu_domain_power_on;
+
+	pm_genpd_init(&domain->base, NULL, !(val & domain->pwr_mask));
+
+	if (np)
+		of_genpd_add_provider_simple(np, &domain->base);
+}
+
+/* PMU IRQ controller */
+static void pmu_irq_handler(unsigned int irq, struct irq_desc *desc)
+{
+	struct pmu_data *pmu = irq_get_handler_data(irq);
+	struct irq_chip_generic *gc = pmu->irq_gc;
+	struct irq_domain *domain = pmu->irq_domain;
+	void __iomem *base = gc->reg_base;
+	u32 stat = readl_relaxed(base + PMC_IRQ_CAUSE) & gc->mask_cache;
+	u32 done = ~0;
+
+	if (stat == 0) {
+		handle_bad_irq(irq, desc);
+		return;
+	}
+
+	while (stat) {
+		u32 hwirq = fls(stat) - 1;
+
+		stat &= ~(1 << hwirq);
+		done &= ~(1 << hwirq);
+
+		generic_handle_irq(irq_find_mapping(domain, hwirq));
+	}
+
+	/*
+	 * The PMU mask register is not RW0C: it is RW.  This means that
+	 * the bits take whatever value is written to them; if you write
+	 * a '1', you will set the interrupt.
+	 *
+	 * Unfortunately this means there is NO race free way to clear
+	 * these interrupts.
+	 *
+	 * So, let's structure the code so that the window is as small as
+	 * possible.
+	 */
+	irq_gc_lock(gc);
+	done &= readl_relaxed(base + PMC_IRQ_CAUSE);
+	writel_relaxed(done, base + PMC_IRQ_CAUSE);
+	irq_gc_unlock(gc);
+}
+
+static int __init dove_init_pmu_irq(struct pmu_data *pmu, int irq)
+{
+	const char *name = "pmu_irq";
+	struct irq_chip_generic *gc;
+	struct irq_domain *domain;
+	int ret;
+
+	/* mask and clear all interrupts */
+	writel(0, pmu->pmc_base + PMC_IRQ_MASK);
+	writel(0, pmu->pmc_base + PMC_IRQ_CAUSE);
+
+	domain = irq_domain_add_linear(pmu->of_node, NR_PMU_IRQS,
+				       &irq_generic_chip_ops, NULL);
+	if (!domain) {
+		pr_err("%s: unable to add irq domain\n", name);
+		return -ENOMEM;
+	}
+
+	ret = irq_alloc_domain_generic_chips(domain, NR_PMU_IRQS, 1, name,
+					     handle_level_irq,
+					     IRQ_NOREQUEST | IRQ_NOPROBE, 0,
+					     IRQ_GC_INIT_MASK_CACHE);
+	if (ret) {
+		pr_err("%s: unable to alloc irq domain gc: %d\n", name, ret);
+		irq_domain_remove(domain);
+		return ret;
+	}
+
+	gc = irq_get_domain_generic_chip(domain, 0);
+	gc->reg_base = pmu->pmc_base;
+	gc->chip_types[0].regs.mask = PMC_IRQ_MASK;
+	gc->chip_types[0].chip.irq_mask = irq_gc_mask_clr_bit;
+	gc->chip_types[0].chip.irq_unmask = irq_gc_mask_set_bit;
+
+	pmu->irq_domain = domain;
+	pmu->irq_gc = gc;
+
+	irq_set_handler_data(irq, pmu);
+	irq_set_chained_handler(irq, pmu_irq_handler);
+
+	return 0;
+}
+
+/*
+ * pmu: power-manager@d0000 {
+ *	compatible = "marvell,dove-pmu";
+ *	reg = <0xd0000 0x8000> <0xd8000 0x8000>;
+ *	interrupts = <33>;
+ *	interrupt-controller;
+ *	#reset-cells = 1;
+ *	vpu_domain: vpu-domain {
+ *		#power-domain-cells = <0>;
+ *		marvell,pmu_pwr_mask = <0x00000008>;
+ *		marvell,pmu_iso_mask = <0x00000001>;
+ *		resets = <&pmu 16>;
+ *	};
+ *	gpu_domain: gpu-domain {
+ *		#power-domain-cells = <0>;
+ *		marvell,pmu_pwr_mask = <0x00000004>;
+ *		marvell,pmu_iso_mask = <0x00000002>;
+ *		resets = <&pmu 18>;
+ *	};
+ * };
+ */
+int __init dove_init_pmu(void)
+{
+	struct device_node *np_pmu, *np;
+	struct pmu_data *pmu;
+	int ret, parent_irq;
+
+	/* Lookup the PMU node */
+	np_pmu = of_find_compatible_node(NULL, NULL, "marvell,dove-pmu");
+	if (!np_pmu)
+		return 0;
+
+	pmu = kzalloc(sizeof(*pmu), GFP_KERNEL);
+	if (!pmu)
+		return -ENOMEM;
+
+	spin_lock_init(&pmu->lock);
+	pmu->of_node = np_pmu;
+	pmu->pmc_base = of_iomap(pmu->of_node, 0);
+	pmu->pmu_base = of_iomap(pmu->of_node, 1);
+	if (!pmu->pmc_base || !pmu->pmu_base) {
+		pr_err("%s: failed to map PMU\n", np_pmu->name);
+		iounmap(pmu->pmu_base);
+		iounmap(pmu->pmc_base);
+		kfree(pmu);
+		return -ENOMEM;
+	}
+
+	parent_irq = irq_of_parse_and_map(pmu->of_node, 0);
+	if (!parent_irq)
+		pr_err("%s: no interrupt specified\n", np_pmu->name);
+
+	pmu_reset_init(pmu);
+
+	for_each_available_child_of_node(pmu->of_node, np) {
+		struct of_phandle_args args;
+		struct pmu_domain *domain;
+
+		domain = kzalloc(sizeof(*domain), GFP_KERNEL);
+		if (!domain)
+			break;
+
+		domain->pmu = pmu;
+		domain->base.name = kstrdup(np->name, GFP_KERNEL);
+		if (!domain->base.name) {
+			kfree(domain);
+			break;
+		}
+
+		of_property_read_u32(np, "marvell,pmu_pwr_mask",
+				     &domain->pwr_mask);
+		of_property_read_u32(np, "marvell,pmu_iso_mask",
+				     &domain->iso_mask);
+
+		ret = of_parse_phandle_with_args(np, "resets", "#reset-cells",
+						 0, &args);
+		if (ret == 0) {
+			if (args.np == pmu->of_node)
+				domain->rst_mask = BIT(args.args[0]);
+			of_node_put(args.np);
+		}
+
+		__pmu_domain_register(domain, np);
+	}
+	pm_genpd_poweroff_unused();
+
+	ret = dove_init_pmu_irq(pmu, parent_irq);
+	if (ret)
+		pr_err("dove_init_pmu_irq() failed: %d\n", ret);
+
+	return 0;
+}
diff --git a/include/linux/soc/dove/pmu.h b/include/linux/soc/dove/pmu.h
new file mode 100644
index 000000000000..9c99f84bcc0e
--- /dev/null
+++ b/include/linux/soc/dove/pmu.h
@@ -0,0 +1,6 @@ 
+#ifndef LINUX_SOC_DOVE_PMU_H
+#define LINUX_SOC_DOVE_PMU_H
+
+int dove_init_pmu(void);
+
+#endif