[PATCH 4/4] irqchip/imx-gpcv2: Add power domain support

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 




Add code allowing for control of various power domains managed by GPCv2
IP block found in i.MX7 series of SoCs. Power domains covered by this
patch are:

      - PCIE PHY
      - MIPI PHY
      - USB HSIC PHY
      - USB OTG1/2 PHY

Support for any other power domain controlled by GPC is not present, and
can be added at some later point.

Testing of this code was done against a PCIe driver.

Cc: yurovsky@xxxxxxxxx
Cc: Shawn Guo <shawnguo@xxxxxxxxxx>
Cc: Thomas Gleixner <tglx@xxxxxxxxxxxxx>
Cc: Jason Cooper <jason@xxxxxxxxxxxxxx>
Cc: Marc Zyngier <marc.zyngier@xxxxxxx>
Cc: Rob Herring <robh+dt@xxxxxxxxxx>
Cc: Mark Rutland <mark.rutland@xxxxxxx>
Cc: devicetree@xxxxxxxxxxxxxxx
Signed-off-by: Andrey Smirnov <andrew.smirnov@xxxxxxxxx>
---
 .../devicetree/bindings/power/fsl,imx-gpcv2.txt    |  63 +++++
 drivers/irqchip/irq-imx-gpcv2.c                    | 263 ++++++++++++++++++++-
 include/dt-bindings/power/imx7-power.h             |  18 ++
 3 files changed, 343 insertions(+), 1 deletion(-)
 create mode 100644 Documentation/devicetree/bindings/power/fsl,imx-gpcv2.txt
 create mode 100644 include/dt-bindings/power/imx7-power.h

diff --git a/Documentation/devicetree/bindings/power/fsl,imx-gpcv2.txt b/Documentation/devicetree/bindings/power/fsl,imx-gpcv2.txt
new file mode 100644
index 0000000..d971006
--- /dev/null
+++ b/Documentation/devicetree/bindings/power/fsl,imx-gpcv2.txt
@@ -0,0 +1,63 @@
+Freescale i.MX General Power Controller v2
+==========================================
+
+The i.MX7S/D General Power Control (GPC) block contains Power Gating
+Control (PGC) for various power domains.
+
+Required properties:
+
+- compatible: Should be "fsl,imx7d-gpc"
+
+- reg: should be register base and length as documented in the
+  datasheet
+
+- interrupts: Should contain GPC interrupt request 1
+
+- pcie-phy-supply: Link to the LDO regulator powering the PCIE PHY
+  power domain (connected to PCIE_VP/VP_TX/VP_RX pads)
+
+- mipi-phy-supply: Link to the LDO regulator powering the MIPI PHY
+  power domain (connected to VDD_MIPI_1P0 pad)
+
+- usb-hsic-phy-supply: Link to the LDO regulator powering the USB HSIC
+  PHY power domain (connected to VDD_USB_H_1P2 pad)
+
+- #power-domain-cells: Should be 1, see below:
+
+The gpc node is a power-controller as documented by the generic power
+domain bindings in
+Documentation/devicetree/bindings/power/power_domain.txt.
+
+Example:
+
+	gpc: gpc@303a0000 {
+		compatible = "fsl,imx7d-gpc";
+		reg = <0x303a0000 0x10000>;
+		interrupt-controller;
+		interrupts = <GIC_SPI 87 IRQ_TYPE_LEVEL_HIGH>;
+		#interrupt-cells = <3>;
+		interrupt-parent = <&intc>;
+		#power-domain-cells = <1>;
+		pcie-phy-supply = <&reg_1p0d>;
+	};
+
+
+Specifying power domain for IP modules
+======================================
+
+IP cores belonging to a power domain should contain a 'power-domains'
+property that is a phandle pointing to the gpc device node and a
+DOMAIN_INDEX specifying the power domain the device belongs to.
+
+Example of a device that is part of the PU power domain:
+
+	pcie: pcie@0x33800000 {
+	      reg = <0x33800000 0x4000>,
+	            <0x4ff00000 0x80000>;
+		/* ... */
+		power-domains = <&gpc IMX7_POWER_DOMAIN_PCIE_PHY>;
+		/* ... */
+	};
+
+All valid DOMAIN_INDEX values are defined and can be found in
+include/dt-bindings/power/imx7-power.h
diff --git a/drivers/irqchip/irq-imx-gpcv2.c b/drivers/irqchip/irq-imx-gpcv2.c
index 15af9a9..c8fe7cd 100644
--- a/drivers/irqchip/irq-imx-gpcv2.c
+++ b/drivers/irqchip/irq-imx-gpcv2.c
@@ -11,6 +11,10 @@
 #include <linux/slab.h>
 #include <linux/irqchip.h>
 #include <linux/syscore_ops.h>
+#include <linux/platform_device.h>
+#include <linux/pm_domain.h>
+#include <linux/regulator/consumer.h>
+#include <dt-bindings/power/imx7-power.h>
 
 #define IMR_NUM			4
 #define GPC_MAX_IRQS            (IMR_NUM * 32)
@@ -18,6 +22,21 @@
 #define GPC_IMR1_CORE0		0x30
 #define GPC_IMR1_CORE1		0x40
 
+#define GPC_PGC_CPU_MAPPING	0xec
+#define USB_HSIC_PHY_A7_DOMAIN	BIT(6)
+#define USB_OTG2_PHY_A7_DOMAIN	BIT(5)
+#define USB_OTG1_PHY_A7_DOMAIN	BIT(4)
+#define PCIE_PHY_A7_DOMAIN	BIT(3)
+#define MIPI_PHY_A7_DOMAIN	BIT(2)
+
+#define GPC_PU_PGC_SW_PUP_REQ	0xf8
+#define GPC_PU_PGC_SW_PDN_REQ	0x104
+#define USB_HSIC_PHY_SW_Pxx_REQ	BIT(4)
+#define USB_OTG2_PHY_SW_Pxx_REQ	BIT(3)
+#define USB_OTG1_PHY_SW_Pxx_REQ	BIT(2)
+#define PCIE_PHY_SW_Pxx_REQ	BIT(1)
+#define MIPI_PHY_SW_Pxx_REQ	BIT(0)
+
 struct gpcv2_irqchip_data {
 	struct raw_spinlock	rlock;
 	void __iomem		*gpc_base;
@@ -26,6 +45,18 @@ struct gpcv2_irqchip_data {
 	u32			cpu2wakeup;
 };
 
+struct gpcv2_domain {
+	struct generic_pm_domain genpd;
+	struct regulator *regulator;
+
+	const struct {
+		u32 pxx;
+		u32 map;
+	} bits;
+
+	struct device *dev;
+};
+
 static struct gpcv2_irqchip_data *imx_gpcv2_instance;
 
 /*
@@ -268,5 +299,235 @@ static int __init imx_gpcv2_irqchip_init(struct device_node *node,
 
 	return 0;
 }
+IRQCHIP_DECLARE_DRIVER(imx_gpcv2, "fsl,imx7d-gpc", imx_gpcv2_irqchip_init);
+
+static int imx7_gpc_pu_pgc_sw_pxx_req(struct generic_pm_domain *genpd,
+				      bool on)
+{
+	int ret = 0;
+	u32 mapping;
+	unsigned long deadline;
+	struct gpcv2_domain *pd = container_of(genpd,
+					       struct gpcv2_domain, genpd);
+	void __iomem *base  = imx_gpcv2_instance->gpc_base;
+	unsigned int offset = (on) ?
+		GPC_PU_PGC_SW_PUP_REQ : GPC_PU_PGC_SW_PDN_REQ;
+
+	if (!base)
+		return -ENODEV;
+
+	mapping = readl_relaxed(base + GPC_PGC_CPU_MAPPING);
+	writel_relaxed(mapping | pd->bits.map, base + GPC_PGC_CPU_MAPPING);
+
+	if (on) {
+		ret = regulator_enable(pd->regulator);
+		if (ret) {
+			dev_err(pd->dev,
+				"failed to enable regulator: %d\n", ret);
+			goto unmap;
+		}
+	}
+
+	writel_relaxed(readl_relaxed(base + offset) | pd->bits.pxx,
+		       base + offset);
+
+	/*
+	 * As per "5.5.9.4 Example Code 4" in IMX7DRM.pdf wait
+	 * for PUP_REQ/PDN_REQ bit to be cleared
+	 */
+	deadline = jiffies + msecs_to_jiffies(1);
+	while (true) {
+		if (readl_relaxed(base + offset) & pd->bits.pxx)
+			break;
+		if (time_after(jiffies, deadline)) {
+			dev_err(pd->dev, "falied to command PGC\n");
+			ret = -ETIMEDOUT;
+			/*
+			 * If we were in a process of enabling a
+			 * domain and failed we might as well disable
+			 * the regulator we just enabled. And if it
+			 * was the opposite situation and we failed to
+			 * power down -- keep the regulator on
+			 */
+			on  = !on;
+			break;
+		}
+		cpu_relax();
+	}
+
+	if (!on) {
+		int err;
+
+		err = regulator_disable(pd->regulator);
+		if (err)
+			dev_err(pd->dev,
+				"failed to disable regulator: %d\n", ret);
+		/*
+		 * Preserve earlier error code
+		 */
+		ret = ret ?: err;
+	}
+unmap:
+	writel_relaxed(mapping, base + GPC_PGC_CPU_MAPPING);
+	return ret;
+}
+
+static int imx7_gpc_pu_pgc_sw_pup_req(struct generic_pm_domain *genpd)
+{
+	return imx7_gpc_pu_pgc_sw_pxx_req(genpd, true);
+}
+
+static int imx7_gpc_pu_pgc_sw_pdn_req(struct generic_pm_domain *genpd)
+{
+	return imx7_gpc_pu_pgc_sw_pxx_req(genpd, false);
+}
 
-IRQCHIP_DECLARE(imx_gpcv2, "fsl,imx7d-gpc", imx_gpcv2_irqchip_init);
+static struct gpcv2_domain imx7_usb_hsic_phy = {
+	.genpd = {
+		.name      = "usb-hsic-phy",
+		.power_on  = imx7_gpc_pu_pgc_sw_pup_req,
+		.power_off = imx7_gpc_pu_pgc_sw_pdn_req,
+	},
+	.bits  = {
+		.pxx = USB_HSIC_PHY_SW_Pxx_REQ,
+		.map = USB_HSIC_PHY_A7_DOMAIN,
+	},
+};
+
+static struct gpcv2_domain imx7_usb_otg2_phy = {
+	.genpd = {
+		.name      = "usb-otg2-phy",
+		.power_on  = imx7_gpc_pu_pgc_sw_pup_req,
+		.power_off = imx7_gpc_pu_pgc_sw_pdn_req,
+	},
+	.bits  = {
+		.pxx = USB_OTG2_PHY_SW_Pxx_REQ,
+		.map = USB_OTG2_PHY_A7_DOMAIN,
+	},
+};
+
+static struct gpcv2_domain imx7_usb_otg1_phy = {
+	.genpd = {
+		.name      = "usb-otg1-phy",
+		.power_on  = imx7_gpc_pu_pgc_sw_pup_req,
+		.power_off = imx7_gpc_pu_pgc_sw_pdn_req,
+	},
+	.bits  = {
+		.pxx = USB_OTG1_PHY_SW_Pxx_REQ,
+		.map = USB_OTG1_PHY_A7_DOMAIN,
+	},
+};
+
+static struct gpcv2_domain imx7_pcie_phy = {
+	.genpd = {
+		.name      = "pcie-phy",
+		.power_on  = imx7_gpc_pu_pgc_sw_pup_req,
+		.power_off = imx7_gpc_pu_pgc_sw_pdn_req,
+	},
+	.bits  = {
+		.pxx = PCIE_PHY_SW_Pxx_REQ,
+		.map = PCIE_PHY_A7_DOMAIN,
+	},
+};
+
+static struct gpcv2_domain imx7_mipi_phy = {
+	.genpd = {
+		.name      = "mipi-phy",
+		.power_on  = imx7_gpc_pu_pgc_sw_pup_req,
+		.power_off = imx7_gpc_pu_pgc_sw_pdn_req,
+	},
+	.bits  = {
+		.pxx = MIPI_PHY_SW_Pxx_REQ,
+		.map = MIPI_PHY_A7_DOMAIN,
+	},
+};
+
+static struct generic_pm_domain *imx_gpcv2_domains[] = {
+	[IMX7_POWER_DOMAIN_USB_HSIC_PHY] = &imx7_usb_hsic_phy.genpd,
+	[IMX7_POWER_DOMAIN_USB_OTG2_PHY] = &imx7_usb_otg2_phy.genpd,
+	[IMX7_POWER_DOMAIN_USB_OTG1_PHY] = &imx7_usb_otg1_phy.genpd,
+	[IMX7_POWER_DOMAIN_PCIE_PHY]     = &imx7_pcie_phy.genpd,
+	[IMX7_POWER_DOMAIN_MIPI_PHY]     = &imx7_mipi_phy.genpd,
+};
+
+static struct genpd_onecell_data imx_gpcv2_onecell_data = {
+	.domains = imx_gpcv2_domains,
+	.num_domains = ARRAY_SIZE(imx_gpcv2_domains),
+};
+
+static int imx_gpcv2_probe(struct platform_device *pdev)
+{
+	int i, ret;
+	struct device *dev = &pdev->dev;
+
+	for (i = 0; i < ARRAY_SIZE(imx_gpcv2_domains); i++) {
+		int voltage = 0;
+		const char *id = "dummy";
+		struct generic_pm_domain *genpd = imx_gpcv2_domains[i];
+		struct gpcv2_domain *pd = container_of(genpd,
+						       struct gpcv2_domain,
+						       genpd);
+
+		ret = pm_genpd_init(genpd, NULL, true);
+		if (ret) {
+			dev_err(dev, "Failed to init power domain #%d\n", i);
+			goto undo_pm_genpd_init;
+		}
+
+		switch (i) {
+		case IMX7_POWER_DOMAIN_PCIE_PHY:
+			id = "pcie-phy";
+			voltage = 1000000;
+			break;
+		case IMX7_POWER_DOMAIN_MIPI_PHY:
+			id = "mipi-phy";
+			voltage = 1000000;
+			break;
+		case IMX7_POWER_DOMAIN_USB_HSIC_PHY:
+			id = "usb-hsic-phy";
+			voltage = 1200000;
+			break;
+		}
+
+		pd->regulator = devm_regulator_get(dev, id);
+		if (voltage)
+			regulator_set_voltage(pd->regulator,
+					      voltage, voltage);
+
+		pd->dev = dev;
+	}
+
+	ret = of_genpd_add_provider_onecell(dev->of_node,
+					    &imx_gpcv2_onecell_data);
+	if (ret) {
+		dev_err(dev, "Failed to add genpd provider\n");
+		goto undo_pm_genpd_init;
+	}
+
+	return 0;
+
+undo_pm_genpd_init:
+	for (--i; i >= 0; i--)
+		pm_genpd_remove(imx_gpcv2_domains[i]);
+
+	return ret;
+}
+
+static const struct of_device_id imx_gpcv2_dt_ids[] = {
+	{ .compatible = "fsl,imx7d-gpc" },
+	{ }
+};
+
+static struct platform_driver imx_gpcv2_driver = {
+	.driver = {
+		.name = "imx-gpcv2",
+		.of_match_table = imx_gpcv2_dt_ids,
+	},
+	.probe = imx_gpcv2_probe,
+};
+
+static int __init imx_pgcv2_init(void)
+{
+	return platform_driver_register(&imx_gpcv2_driver);
+}
+subsys_initcall(imx_pgcv2_init);
diff --git a/include/dt-bindings/power/imx7-power.h b/include/dt-bindings/power/imx7-power.h
new file mode 100644
index 0000000..24dde62
--- /dev/null
+++ b/include/dt-bindings/power/imx7-power.h
@@ -0,0 +1,18 @@
+/*
+ *  Copyright © 2017 Impinj
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef __DT_BINDINGS_ARM_IMX7_POWER_H__
+#define __DT_BINDINGS_ARM_IMX7_POWER_H__
+
+#define IMX7_POWER_DOMAIN_USB_HSIC_PHY	0
+#define IMX7_POWER_DOMAIN_USB_OTG2_PHY	1
+#define IMX7_POWER_DOMAIN_USB_OTG1_PHY	2
+#define IMX7_POWER_DOMAIN_PCIE_PHY	3
+#define IMX7_POWER_DOMAIN_MIPI_PHY	4
+
+#endif
-- 
2.9.3

--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html



[Index of Archives]     [Device Tree Compilter]     [Device Tree Spec]     [Linux Driver Backports]     [Video for Linux]     [Linux USB Devel]     [Linux PCI Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [XFree86]     [Yosemite Backpacking]
  Powered by Linux