i.MX drivers updates for 4.12:
- A series from Lucas Stach which partly rewrites the imx gpc driver to support multiple power domains, and moves the related code from imx platform into drivers folder. - A series from Dong Aisheng which fixes the issues with Lucas' code changes and improves things. - Add workaround for i.MX6QP hardware erratum ERR009619 that is PRE clocks may be stalled during the power up sequencing of the PU power domain. - Add imx-gpcv2 driver to support power domains managed by GPCv2 IP block found on i.MX7 series of SoCs. -----BEGIN PGP SIGNATURE----- Version: GnuPG v1 iQEcBAABAgAGBQJY6zd4AAoJEFBXWFqHsHzODtoH/jwTFRhAhfKG2wfplkP+U4HT gijXHDybebRGll2voGAL7YX7GPhLcy0NSGdiK/ckTtHlvR4E2uxcwaIwzXIE1CTb MXfv7p+3FXhjcqgbvl122hnLAMWOYjcbH3tFzBy1jlpGp9oEmHx4OtpdlgyD5GJ7 +En6aDtbU0g8aJg9ldZfO8iSAJz1CAqpap+FRjpcPX4xXflOQwQfxJNWuDeg75k6 U3zB2r+lbmjIfohUHWAnHsU2Jf4WPrBrjVsj/1YsiE5rMNPfxAEPYSQJAumG7JtO qTPWf3qJnSyrWBscPRR3Fs//11IgXZXSCWBPBZjng0ELFS3Rkdvp87etjZraSjU= =Sypm -----END PGP SIGNATURE----- Merge tag 'imx-drivers-4.12' of git://git.kernel.org/pub/scm/linux/kernel/git/shawnguo/linux into next/drivers i.MX drivers updates for 4.12: - A series from Lucas Stach which partly rewrites the imx gpc driver to support multiple power domains, and moves the related code from imx platform into drivers folder. - A series from Dong Aisheng which fixes the issues with Lucas' code changes and improves things. - Add workaround for i.MX6QP hardware erratum ERR009619 that is PRE clocks may be stalled during the power up sequencing of the PU power domain. - Add imx-gpcv2 driver to support power domains managed by GPCv2 IP block found on i.MX7 series of SoCs. * tag 'imx-drivers-4.12' of git://git.kernel.org/pub/scm/linux/kernel/git/shawnguo/linux: soc: imx: gpc: add workaround for i.MX6QP to the GPC PD driver dt-bindings: imx-gpc: add i.MX6 QuadPlus compatible soc: imx: gpc: add defines for domain index soc: imx: Add GPCv2 power gating driver dt-bindings: Add GPCv2 power gating driver soc: imx: gpc: remove unnecessary readable_reg callback dt-bindings: imx-gpc: correct the DOMAIN_INDEX using soc: imx: gpc: keep PGC_X_CTRL name align with reference manual soc: imx: gpc: fix comment when power up domain soc: imx: gpc: fix imx6sl gpc power domain regression soc: imx: gpc: fix domain_index sanity check issue soc: imx: gpc: fix the wrong using of regmap cache soc: imx: gpc: fix gpc clk get error handling soc: imx: move PGC handling to a new GPC driver dt-bindings: add multidomain support to i.MX GPC DT binding Signed-off-by: Olof Johansson <olof@lixom.net>
This commit is contained in:
commit
912c9fbe66
|
@ -1,22 +1,42 @@
|
|||
Freescale i.MX General Power Controller
|
||||
=======================================
|
||||
|
||||
The i.MX6Q General Power Control (GPC) block contains DVFS load tracking
|
||||
counters and Power Gating Control (PGC) for the CPU and PU (GPU/VPU) power
|
||||
domains.
|
||||
The i.MX6 General Power Control (GPC) block contains DVFS load tracking
|
||||
counters and Power Gating Control (PGC).
|
||||
|
||||
Required properties:
|
||||
- compatible: Should be "fsl,imx6q-gpc" or "fsl,imx6sl-gpc"
|
||||
- compatible: Should be one of the following:
|
||||
- fsl,imx6q-gpc
|
||||
- fsl,imx6qp-gpc
|
||||
- fsl,imx6sl-gpc
|
||||
- reg: should be register base and length as documented in the
|
||||
datasheet
|
||||
- interrupts: Should contain GPC interrupt request 1
|
||||
- pu-supply: Link to the LDO regulator powering the PU power domain
|
||||
- clocks: Clock phandles to devices in the PU power domain that need
|
||||
to be enabled during domain power-up for reset propagation.
|
||||
- #power-domain-cells: Should be 1, see below:
|
||||
- interrupts: Should contain one interrupt specifier for the GPC interrupt
|
||||
- clocks: Must contain an entry for each entry in clock-names.
|
||||
See Documentation/devicetree/bindings/clocks/clock-bindings.txt for details.
|
||||
- clock-names: Must include the following entries:
|
||||
- ipg
|
||||
|
||||
The gpc node is a power-controller as documented by the generic power domain
|
||||
bindings in Documentation/devicetree/bindings/power/power_domain.txt.
|
||||
The power domains are generic power domain providers as documented in
|
||||
Documentation/devicetree/bindings/power/power_domain.txt. They are described as
|
||||
subnodes of the power gating controller 'pgc' node of the GPC and should
|
||||
contain the following:
|
||||
|
||||
Required properties:
|
||||
- reg: Must contain the DOMAIN_INDEX of this power domain
|
||||
The following DOMAIN_INDEX values are valid for i.MX6Q:
|
||||
ARM_DOMAIN 0
|
||||
PU_DOMAIN 1
|
||||
The following additional DOMAIN_INDEX value is valid for i.MX6SL:
|
||||
DISPLAY_DOMAIN 2
|
||||
|
||||
- #power-domain-cells: Should be 0
|
||||
|
||||
Optional properties:
|
||||
- clocks: a number of phandles to clocks that need to be enabled during domain
|
||||
power-up sequencing to ensure reset propagation into devices located inside
|
||||
this power domain
|
||||
- power-supply: a phandle to the regulator powering this domain
|
||||
|
||||
Example:
|
||||
|
||||
|
@ -25,14 +45,30 @@ Example:
|
|||
reg = <0x020dc000 0x4000>;
|
||||
interrupts = <0 89 IRQ_TYPE_LEVEL_HIGH>,
|
||||
<0 90 IRQ_TYPE_LEVEL_HIGH>;
|
||||
pu-supply = <®_pu>;
|
||||
clocks = <&clks IMX6QDL_CLK_GPU3D_CORE>,
|
||||
<&clks IMX6QDL_CLK_GPU3D_SHADER>,
|
||||
<&clks IMX6QDL_CLK_GPU2D_CORE>,
|
||||
<&clks IMX6QDL_CLK_GPU2D_AXI>,
|
||||
<&clks IMX6QDL_CLK_OPENVG_AXI>,
|
||||
<&clks IMX6QDL_CLK_VPU_AXI>;
|
||||
#power-domain-cells = <1>;
|
||||
clocks = <&clks IMX6QDL_CLK_IPG>;
|
||||
clock-names = "ipg";
|
||||
|
||||
pgc {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
power-domain@0 {
|
||||
reg = <0>;
|
||||
#power-domain-cells = <0>;
|
||||
};
|
||||
|
||||
pd_pu: power-domain@1 {
|
||||
reg = <1>;
|
||||
#power-domain-cells = <0>;
|
||||
power-supply = <®_pu>;
|
||||
clocks = <&clks IMX6QDL_CLK_GPU3D_CORE>,
|
||||
<&clks IMX6QDL_CLK_GPU3D_SHADER>,
|
||||
<&clks IMX6QDL_CLK_GPU2D_CORE>,
|
||||
<&clks IMX6QDL_CLK_GPU2D_AXI>,
|
||||
<&clks IMX6QDL_CLK_OPENVG_AXI>,
|
||||
<&clks IMX6QDL_CLK_VPU_AXI>;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
|
@ -40,20 +76,13 @@ 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.
|
||||
that is a phandle pointing to the power domain the device belongs to.
|
||||
|
||||
Example of a device that is part of the PU power domain:
|
||||
|
||||
vpu: vpu@02040000 {
|
||||
reg = <0x02040000 0x3c000>;
|
||||
/* ... */
|
||||
power-domains = <&gpc 1>;
|
||||
power-domains = <&pd_pu>;
|
||||
/* ... */
|
||||
};
|
||||
|
||||
The following DOMAIN_INDEX values are valid for i.MX6Q:
|
||||
ARM_DOMAIN 0
|
||||
PU_DOMAIN 1
|
||||
The following additional DOMAIN_INDEX value is valid for i.MX6SL:
|
||||
DISPLAY_DOMAIN 2
|
||||
|
|
71
Documentation/devicetree/bindings/power/fsl,imx-gpcv2.txt
Normal file
71
Documentation/devicetree/bindings/power/fsl,imx-gpcv2.txt
Normal file
|
@ -0,0 +1,71 @@
|
|||
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
|
||||
|
||||
Power domains contained within GPC node are generic power domain
|
||||
providers, documented in
|
||||
Documentation/devicetree/bindings/power/power_domain.txt, which are
|
||||
described as subnodes of the power gating controller 'pgc' node,
|
||||
which, in turn, is expected to contain the following:
|
||||
|
||||
Required properties:
|
||||
|
||||
- reg: Power domain index. Valid values are defined in
|
||||
include/dt-bindings/power/imx7-power.h
|
||||
|
||||
- #power-domain-cells: Should be 0
|
||||
|
||||
Optional properties:
|
||||
|
||||
- power-supply: Power supply used to power the domain
|
||||
|
||||
Example:
|
||||
|
||||
gpc: gpc@303a0000 {
|
||||
compatible = "fsl,imx7d-gpc";
|
||||
reg = <0x303a0000 0x1000>;
|
||||
interrupt-controller;
|
||||
interrupts = <GIC_SPI 87 IRQ_TYPE_LEVEL_HIGH>;
|
||||
#interrupt-cells = <3>;
|
||||
interrupt-parent = <&intc>;
|
||||
|
||||
pgc {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
pgc_pcie_phy: power-domain@3 {
|
||||
#power-domain-cells = <0>;
|
||||
|
||||
reg = <IMX7_POWER_DOMAIN_PCIE_PHY>;
|
||||
power-supply = <®_1p0d>;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
Specifying power domain for IP modules
|
||||
======================================
|
||||
|
||||
IP cores belonging to a power domain should contain a 'power-domains'
|
||||
property that is a phandle for PGC node representing the domain.
|
||||
|
||||
Example of a device that is part of the PCIE_PHY power domain:
|
||||
|
||||
pcie: pcie@33800000 {
|
||||
reg = <0x33800000 0x4000>,
|
||||
<0x4ff00000 0x80000>;
|
||||
/* ... */
|
||||
power-domains = <&pgc_pcie_phy>;
|
||||
/* ... */
|
||||
};
|
|
@ -1268,6 +1268,7 @@ F: arch/arm/mach-mxs/
|
|||
F: arch/arm/boot/dts/imx*
|
||||
F: arch/arm/configs/imx*_defconfig
|
||||
F: drivers/clk/imx/
|
||||
F: drivers/soc/imx/
|
||||
F: include/soc/imx/
|
||||
|
||||
ARM/FREESCALE VYBRID ARM ARCHITECTURE
|
||||
|
|
|
@ -10,26 +10,17 @@
|
|||
* http://www.gnu.org/copyleft/gpl.html
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/irqchip.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/of_irq.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pm_domain.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/irqchip/arm-gic.h>
|
||||
#include "common.h"
|
||||
#include "hardware.h"
|
||||
|
||||
#define GPC_CNTR 0x000
|
||||
#define GPC_IMR1 0x008
|
||||
#define GPC_PGC_GPU_PDN 0x260
|
||||
#define GPC_PGC_GPU_PUPSCR 0x264
|
||||
#define GPC_PGC_GPU_PDNSCR 0x268
|
||||
#define GPC_PGC_CPU_PDN 0x2a0
|
||||
#define GPC_PGC_CPU_PUPSCR 0x2a4
|
||||
#define GPC_PGC_CPU_PDNSCR 0x2a8
|
||||
|
@ -39,18 +30,6 @@
|
|||
#define IMR_NUM 4
|
||||
#define GPC_MAX_IRQS (IMR_NUM * 32)
|
||||
|
||||
#define GPU_VPU_PUP_REQ BIT(1)
|
||||
#define GPU_VPU_PDN_REQ BIT(0)
|
||||
|
||||
#define GPC_CLK_MAX 6
|
||||
|
||||
struct pu_domain {
|
||||
struct generic_pm_domain base;
|
||||
struct regulator *reg;
|
||||
struct clk *clk[GPC_CLK_MAX];
|
||||
int num_clks;
|
||||
};
|
||||
|
||||
static void __iomem *gpc_base;
|
||||
static u32 gpc_wake_irqs[IMR_NUM];
|
||||
static u32 gpc_saved_imrs[IMR_NUM];
|
||||
|
@ -296,199 +275,3 @@ void __init imx_gpc_check_dt(void)
|
|||
gpc_base = of_iomap(np, 0);
|
||||
}
|
||||
}
|
||||
|
||||
static void _imx6q_pm_pu_power_off(struct generic_pm_domain *genpd)
|
||||
{
|
||||
int iso, iso2sw;
|
||||
u32 val;
|
||||
|
||||
/* Read ISO and ISO2SW power down delays */
|
||||
val = readl_relaxed(gpc_base + GPC_PGC_GPU_PDNSCR);
|
||||
iso = val & 0x3f;
|
||||
iso2sw = (val >> 8) & 0x3f;
|
||||
|
||||
/* Gate off PU domain when GPU/VPU when powered down */
|
||||
writel_relaxed(0x1, gpc_base + GPC_PGC_GPU_PDN);
|
||||
|
||||
/* Request GPC to power down GPU/VPU */
|
||||
val = readl_relaxed(gpc_base + GPC_CNTR);
|
||||
val |= GPU_VPU_PDN_REQ;
|
||||
writel_relaxed(val, gpc_base + GPC_CNTR);
|
||||
|
||||
/* Wait ISO + ISO2SW IPG clock cycles */
|
||||
ndelay((iso + iso2sw) * 1000 / 66);
|
||||
}
|
||||
|
||||
static int imx6q_pm_pu_power_off(struct generic_pm_domain *genpd)
|
||||
{
|
||||
struct pu_domain *pu = container_of(genpd, struct pu_domain, base);
|
||||
|
||||
_imx6q_pm_pu_power_off(genpd);
|
||||
|
||||
if (pu->reg)
|
||||
regulator_disable(pu->reg);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int imx6q_pm_pu_power_on(struct generic_pm_domain *genpd)
|
||||
{
|
||||
struct pu_domain *pu = container_of(genpd, struct pu_domain, base);
|
||||
int i, ret, sw, sw2iso;
|
||||
u32 val;
|
||||
|
||||
if (pu->reg)
|
||||
ret = regulator_enable(pu->reg);
|
||||
if (pu->reg && ret) {
|
||||
pr_err("%s: failed to enable regulator: %d\n", __func__, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Enable reset clocks for all devices in the PU domain */
|
||||
for (i = 0; i < pu->num_clks; i++)
|
||||
clk_prepare_enable(pu->clk[i]);
|
||||
|
||||
/* Gate off PU domain when GPU/VPU when powered down */
|
||||
writel_relaxed(0x1, gpc_base + GPC_PGC_GPU_PDN);
|
||||
|
||||
/* Read ISO and ISO2SW power down delays */
|
||||
val = readl_relaxed(gpc_base + GPC_PGC_GPU_PUPSCR);
|
||||
sw = val & 0x3f;
|
||||
sw2iso = (val >> 8) & 0x3f;
|
||||
|
||||
/* Request GPC to power up GPU/VPU */
|
||||
val = readl_relaxed(gpc_base + GPC_CNTR);
|
||||
val |= GPU_VPU_PUP_REQ;
|
||||
writel_relaxed(val, gpc_base + GPC_CNTR);
|
||||
|
||||
/* Wait ISO + ISO2SW IPG clock cycles */
|
||||
ndelay((sw + sw2iso) * 1000 / 66);
|
||||
|
||||
/* Disable reset clocks for all devices in the PU domain */
|
||||
for (i = 0; i < pu->num_clks; i++)
|
||||
clk_disable_unprepare(pu->clk[i]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct generic_pm_domain imx6q_arm_domain = {
|
||||
.name = "ARM",
|
||||
};
|
||||
|
||||
static struct pu_domain imx6q_pu_domain = {
|
||||
.base = {
|
||||
.name = "PU",
|
||||
.power_off = imx6q_pm_pu_power_off,
|
||||
.power_on = imx6q_pm_pu_power_on,
|
||||
},
|
||||
};
|
||||
|
||||
static struct generic_pm_domain imx6sl_display_domain = {
|
||||
.name = "DISPLAY",
|
||||
};
|
||||
|
||||
static struct generic_pm_domain *imx_gpc_domains[] = {
|
||||
&imx6q_arm_domain,
|
||||
&imx6q_pu_domain.base,
|
||||
&imx6sl_display_domain,
|
||||
};
|
||||
|
||||
static struct genpd_onecell_data imx_gpc_onecell_data = {
|
||||
.domains = imx_gpc_domains,
|
||||
.num_domains = ARRAY_SIZE(imx_gpc_domains),
|
||||
};
|
||||
|
||||
static int imx_gpc_genpd_init(struct device *dev, struct regulator *pu_reg)
|
||||
{
|
||||
struct clk *clk;
|
||||
int i, ret;
|
||||
|
||||
imx6q_pu_domain.reg = pu_reg;
|
||||
|
||||
for (i = 0; ; i++) {
|
||||
clk = of_clk_get(dev->of_node, i);
|
||||
if (IS_ERR(clk))
|
||||
break;
|
||||
if (i >= GPC_CLK_MAX) {
|
||||
dev_err(dev, "more than %d clocks\n", GPC_CLK_MAX);
|
||||
goto clk_err;
|
||||
}
|
||||
imx6q_pu_domain.clk[i] = clk;
|
||||
}
|
||||
imx6q_pu_domain.num_clks = i;
|
||||
|
||||
/* Enable power always in case bootloader disabled it. */
|
||||
imx6q_pm_pu_power_on(&imx6q_pu_domain.base);
|
||||
|
||||
if (!IS_ENABLED(CONFIG_PM_GENERIC_DOMAINS))
|
||||
return 0;
|
||||
|
||||
imx6q_pu_domain.base.states = devm_kzalloc(dev,
|
||||
sizeof(*imx6q_pu_domain.base.states),
|
||||
GFP_KERNEL);
|
||||
if (!imx6q_pu_domain.base.states)
|
||||
return -ENOMEM;
|
||||
|
||||
imx6q_pu_domain.base.states[0].power_off_latency_ns = 25000;
|
||||
imx6q_pu_domain.base.states[0].power_on_latency_ns = 2000000;
|
||||
imx6q_pu_domain.base.state_count = 1;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(imx_gpc_domains); i++)
|
||||
pm_genpd_init(imx_gpc_domains[i], NULL, false);
|
||||
|
||||
ret = of_genpd_add_provider_onecell(dev->of_node,
|
||||
&imx_gpc_onecell_data);
|
||||
if (ret)
|
||||
goto power_off;
|
||||
|
||||
return 0;
|
||||
|
||||
power_off:
|
||||
imx6q_pm_pu_power_off(&imx6q_pu_domain.base);
|
||||
clk_err:
|
||||
while (i--)
|
||||
clk_put(imx6q_pu_domain.clk[i]);
|
||||
imx6q_pu_domain.reg = NULL;
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int imx_gpc_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct regulator *pu_reg;
|
||||
int ret;
|
||||
|
||||
/* bail out if DT too old and doesn't provide the necessary info */
|
||||
if (!of_property_read_bool(pdev->dev.of_node, "#power-domain-cells"))
|
||||
return 0;
|
||||
|
||||
pu_reg = devm_regulator_get_optional(&pdev->dev, "pu");
|
||||
if (PTR_ERR(pu_reg) == -ENODEV)
|
||||
pu_reg = NULL;
|
||||
if (IS_ERR(pu_reg)) {
|
||||
ret = PTR_ERR(pu_reg);
|
||||
dev_err(&pdev->dev, "failed to get pu regulator: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return imx_gpc_genpd_init(&pdev->dev, pu_reg);
|
||||
}
|
||||
|
||||
static const struct of_device_id imx_gpc_dt_ids[] = {
|
||||
{ .compatible = "fsl,imx6q-gpc" },
|
||||
{ .compatible = "fsl,imx6sl-gpc" },
|
||||
{ }
|
||||
};
|
||||
|
||||
static struct platform_driver imx_gpc_driver = {
|
||||
.driver = {
|
||||
.name = "imx-gpc",
|
||||
.of_match_table = imx_gpc_dt_ids,
|
||||
},
|
||||
.probe = imx_gpc_probe,
|
||||
};
|
||||
|
||||
static int __init imx_pgc_init(void)
|
||||
{
|
||||
return platform_driver_register(&imx_gpc_driver);
|
||||
}
|
||||
subsys_initcall(imx_pgc_init);
|
||||
|
|
|
@ -2,6 +2,7 @@ menu "SOC (System On Chip) specific Drivers"
|
|||
|
||||
source "drivers/soc/bcm/Kconfig"
|
||||
source "drivers/soc/fsl/Kconfig"
|
||||
source "drivers/soc/imx/Kconfig"
|
||||
source "drivers/soc/mediatek/Kconfig"
|
||||
source "drivers/soc/qcom/Kconfig"
|
||||
source "drivers/soc/rockchip/Kconfig"
|
||||
|
|
|
@ -6,6 +6,7 @@ obj-y += bcm/
|
|||
obj-$(CONFIG_ARCH_DOVE) += dove/
|
||||
obj-$(CONFIG_MACH_DOVE) += dove/
|
||||
obj-y += fsl/
|
||||
obj-$(CONFIG_ARCH_MXC) += imx/
|
||||
obj-$(CONFIG_ARCH_MEDIATEK) += mediatek/
|
||||
obj-$(CONFIG_ARCH_QCOM) += qcom/
|
||||
obj-$(CONFIG_ARCH_RENESAS) += renesas/
|
||||
|
|
9
drivers/soc/imx/Kconfig
Normal file
9
drivers/soc/imx/Kconfig
Normal file
|
@ -0,0 +1,9 @@
|
|||
menu "i.MX SoC drivers"
|
||||
|
||||
config IMX7_PM_DOMAINS
|
||||
bool "i.MX7 PM domains"
|
||||
select PM_GENERIC_DOMAINS
|
||||
depends on SOC_IMX7D || (COMPILE_TEST && OF)
|
||||
default y if SOC_IMX7D
|
||||
|
||||
endmenu
|
2
drivers/soc/imx/Makefile
Normal file
2
drivers/soc/imx/Makefile
Normal file
|
@ -0,0 +1,2 @@
|
|||
obj-y += gpc.o
|
||||
obj-$(CONFIG_IMX7_PM_DOMAINS) += gpcv2.o
|
489
drivers/soc/imx/gpc.c
Normal file
489
drivers/soc/imx/gpc.c
Normal file
|
@ -0,0 +1,489 @@
|
|||
/*
|
||||
* Copyright 2015-2017 Pengutronix, Lucas Stach <kernel@pengutronix.de>
|
||||
* Copyright 2011-2013 Freescale Semiconductor, Inc.
|
||||
*
|
||||
* The code contained herein is licensed under the GNU General Public
|
||||
* License. You may obtain a copy of the GNU General Public License
|
||||
* Version 2 or later at the following locations:
|
||||
*
|
||||
* http://www.opensource.org/licenses/gpl-license.html
|
||||
* http://www.gnu.org/copyleft/gpl.html
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pm_domain.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
|
||||
#define GPC_CNTR 0x000
|
||||
|
||||
#define GPC_PGC_CTRL_OFFS 0x0
|
||||
#define GPC_PGC_PUPSCR_OFFS 0x4
|
||||
#define GPC_PGC_PDNSCR_OFFS 0x8
|
||||
#define GPC_PGC_SW2ISO_SHIFT 0x8
|
||||
#define GPC_PGC_SW_SHIFT 0x0
|
||||
|
||||
#define GPC_PGC_GPU_PDN 0x260
|
||||
#define GPC_PGC_GPU_PUPSCR 0x264
|
||||
#define GPC_PGC_GPU_PDNSCR 0x268
|
||||
|
||||
#define GPU_VPU_PUP_REQ BIT(1)
|
||||
#define GPU_VPU_PDN_REQ BIT(0)
|
||||
|
||||
#define GPC_CLK_MAX 6
|
||||
|
||||
#define PGC_DOMAIN_FLAG_NO_PD BIT(0)
|
||||
|
||||
struct imx_pm_domain {
|
||||
struct generic_pm_domain base;
|
||||
struct regmap *regmap;
|
||||
struct regulator *supply;
|
||||
struct clk *clk[GPC_CLK_MAX];
|
||||
int num_clks;
|
||||
unsigned int reg_offs;
|
||||
signed char cntr_pdn_bit;
|
||||
unsigned int ipg_rate_mhz;
|
||||
unsigned int flags;
|
||||
};
|
||||
|
||||
static inline struct imx_pm_domain *
|
||||
to_imx_pm_domain(struct generic_pm_domain *genpd)
|
||||
{
|
||||
return container_of(genpd, struct imx_pm_domain, base);
|
||||
}
|
||||
|
||||
static int imx6_pm_domain_power_off(struct generic_pm_domain *genpd)
|
||||
{
|
||||
struct imx_pm_domain *pd = to_imx_pm_domain(genpd);
|
||||
int iso, iso2sw;
|
||||
u32 val;
|
||||
|
||||
if (pd->flags & PGC_DOMAIN_FLAG_NO_PD)
|
||||
return -EBUSY;
|
||||
|
||||
/* Read ISO and ISO2SW power down delays */
|
||||
regmap_read(pd->regmap, pd->reg_offs + GPC_PGC_PUPSCR_OFFS, &val);
|
||||
iso = val & 0x3f;
|
||||
iso2sw = (val >> 8) & 0x3f;
|
||||
|
||||
/* Gate off domain when powered down */
|
||||
regmap_update_bits(pd->regmap, pd->reg_offs + GPC_PGC_CTRL_OFFS,
|
||||
0x1, 0x1);
|
||||
|
||||
/* Request GPC to power down domain */
|
||||
val = BIT(pd->cntr_pdn_bit);
|
||||
regmap_update_bits(pd->regmap, GPC_CNTR, val, val);
|
||||
|
||||
/* Wait ISO + ISO2SW IPG clock cycles */
|
||||
udelay(DIV_ROUND_UP(iso + iso2sw, pd->ipg_rate_mhz));
|
||||
|
||||
if (pd->supply)
|
||||
regulator_disable(pd->supply);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int imx6_pm_domain_power_on(struct generic_pm_domain *genpd)
|
||||
{
|
||||
struct imx_pm_domain *pd = to_imx_pm_domain(genpd);
|
||||
int i, ret, sw, sw2iso;
|
||||
u32 val;
|
||||
|
||||
if (pd->supply) {
|
||||
ret = regulator_enable(pd->supply);
|
||||
if (ret) {
|
||||
pr_err("%s: failed to enable regulator: %d\n",
|
||||
__func__, ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
/* Enable reset clocks for all devices in the domain */
|
||||
for (i = 0; i < pd->num_clks; i++)
|
||||
clk_prepare_enable(pd->clk[i]);
|
||||
|
||||
/* Gate off domain when powered down */
|
||||
regmap_update_bits(pd->regmap, pd->reg_offs + GPC_PGC_CTRL_OFFS,
|
||||
0x1, 0x1);
|
||||
|
||||
/* Read ISO and ISO2SW power up delays */
|
||||
regmap_read(pd->regmap, pd->reg_offs + GPC_PGC_PUPSCR_OFFS, &val);
|
||||
sw = val & 0x3f;
|
||||
sw2iso = (val >> 8) & 0x3f;
|
||||
|
||||
/* Request GPC to power up domain */
|
||||
val = BIT(pd->cntr_pdn_bit + 1);
|
||||
regmap_update_bits(pd->regmap, GPC_CNTR, val, val);
|
||||
|
||||
/* Wait ISO + ISO2SW IPG clock cycles */
|
||||
udelay(DIV_ROUND_UP(sw + sw2iso, pd->ipg_rate_mhz));
|
||||
|
||||
/* Disable reset clocks for all devices in the domain */
|
||||
for (i = 0; i < pd->num_clks; i++)
|
||||
clk_disable_unprepare(pd->clk[i]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int imx_pgc_get_clocks(struct device *dev, struct imx_pm_domain *domain)
|
||||
{
|
||||
int i, ret;
|
||||
|
||||
for (i = 0; ; i++) {
|
||||
struct clk *clk = of_clk_get(dev->of_node, i);
|
||||
if (IS_ERR(clk))
|
||||
break;
|
||||
if (i >= GPC_CLK_MAX) {
|
||||
dev_err(dev, "more than %d clocks\n", GPC_CLK_MAX);
|
||||
ret = -EINVAL;
|
||||
goto clk_err;
|
||||
}
|
||||
domain->clk[i] = clk;
|
||||
}
|
||||
domain->num_clks = i;
|
||||
|
||||
return 0;
|
||||
|
||||
clk_err:
|
||||
while (i--)
|
||||
clk_put(domain->clk[i]);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void imx_pgc_put_clocks(struct imx_pm_domain *domain)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = domain->num_clks - 1; i >= 0; i--)
|
||||
clk_put(domain->clk[i]);
|
||||
}
|
||||
|
||||
static int imx_pgc_parse_dt(struct device *dev, struct imx_pm_domain *domain)
|
||||
{
|
||||
/* try to get the domain supply regulator */
|
||||
domain->supply = devm_regulator_get_optional(dev, "power");
|
||||
if (IS_ERR(domain->supply)) {
|
||||
if (PTR_ERR(domain->supply) == -ENODEV)
|
||||
domain->supply = NULL;
|
||||
else
|
||||
return PTR_ERR(domain->supply);
|
||||
}
|
||||
|
||||
/* try to get all clocks needed for reset propagation */
|
||||
return imx_pgc_get_clocks(dev, domain);
|
||||
}
|
||||
|
||||
static int imx_pgc_power_domain_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct imx_pm_domain *domain = pdev->dev.platform_data;
|
||||
struct device *dev = &pdev->dev;
|
||||
int ret;
|
||||
|
||||
/* if this PD is associated with a DT node try to parse it */
|
||||
if (dev->of_node) {
|
||||
ret = imx_pgc_parse_dt(dev, domain);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* initially power on the domain */
|
||||
if (domain->base.power_on)
|
||||
domain->base.power_on(&domain->base);
|
||||
|
||||
if (IS_ENABLED(CONFIG_PM_GENERIC_DOMAINS)) {
|
||||
pm_genpd_init(&domain->base, NULL, false);
|
||||
ret = of_genpd_add_provider_simple(dev->of_node, &domain->base);
|
||||
if (ret)
|
||||
goto genpd_err;
|
||||
}
|
||||
|
||||
device_link_add(dev, dev->parent, DL_FLAG_AUTOREMOVE);
|
||||
|
||||
return 0;
|
||||
|
||||
genpd_err:
|
||||
pm_genpd_remove(&domain->base);
|
||||
imx_pgc_put_clocks(domain);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int imx_pgc_power_domain_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct imx_pm_domain *domain = pdev->dev.platform_data;
|
||||
|
||||
if (IS_ENABLED(CONFIG_PM_GENERIC_DOMAINS)) {
|
||||
of_genpd_del_provider(pdev->dev.of_node);
|
||||
pm_genpd_remove(&domain->base);
|
||||
imx_pgc_put_clocks(domain);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct platform_device_id imx_pgc_power_domain_id[] = {
|
||||
{ "imx-pgc-power-domain"},
|
||||
{ },
|
||||
};
|
||||
|
||||
static struct platform_driver imx_pgc_power_domain_driver = {
|
||||
.driver = {
|
||||
.name = "imx-pgc-pd",
|
||||
},
|
||||
.probe = imx_pgc_power_domain_probe,
|
||||
.remove = imx_pgc_power_domain_remove,
|
||||
.id_table = imx_pgc_power_domain_id,
|
||||
};
|
||||
builtin_platform_driver(imx_pgc_power_domain_driver)
|
||||
|
||||
#define GPC_PGC_DOMAIN_ARM 0
|
||||
#define GPC_PGC_DOMAIN_PU 1
|
||||
#define GPC_PGC_DOMAIN_DISPLAY 2
|
||||
|
||||
static struct genpd_power_state imx6_pm_domain_pu_state = {
|
||||
.power_off_latency_ns = 25000,
|
||||
.power_on_latency_ns = 2000000,
|
||||
};
|
||||
|
||||
static struct imx_pm_domain imx_gpc_domains[] = {
|
||||
{
|
||||
.base = {
|
||||
.name = "ARM",
|
||||
},
|
||||
}, {
|
||||
.base = {
|
||||
.name = "PU",
|
||||
.power_off = imx6_pm_domain_power_off,
|
||||
.power_on = imx6_pm_domain_power_on,
|
||||
.states = &imx6_pm_domain_pu_state,
|
||||
.state_count = 1,
|
||||
},
|
||||
.reg_offs = 0x260,
|
||||
.cntr_pdn_bit = 0,
|
||||
}, {
|
||||
.base = {
|
||||
.name = "DISPLAY",
|
||||
.power_off = imx6_pm_domain_power_off,
|
||||
.power_on = imx6_pm_domain_power_on,
|
||||
},
|
||||
.reg_offs = 0x240,
|
||||
.cntr_pdn_bit = 4,
|
||||
}
|
||||
};
|
||||
|
||||
struct imx_gpc_dt_data {
|
||||
int num_domains;
|
||||
bool err009619_present;
|
||||
};
|
||||
|
||||
static const struct imx_gpc_dt_data imx6q_dt_data = {
|
||||
.num_domains = 2,
|
||||
.err009619_present = false,
|
||||
};
|
||||
|
||||
static const struct imx_gpc_dt_data imx6qp_dt_data = {
|
||||
.num_domains = 2,
|
||||
.err009619_present = true,
|
||||
};
|
||||
|
||||
static const struct imx_gpc_dt_data imx6sl_dt_data = {
|
||||
.num_domains = 3,
|
||||
.err009619_present = false,
|
||||
};
|
||||
|
||||
static const struct of_device_id imx_gpc_dt_ids[] = {
|
||||
{ .compatible = "fsl,imx6q-gpc", .data = &imx6q_dt_data },
|
||||
{ .compatible = "fsl,imx6qp-gpc", .data = &imx6qp_dt_data },
|
||||
{ .compatible = "fsl,imx6sl-gpc", .data = &imx6sl_dt_data },
|
||||
{ }
|
||||
};
|
||||
|
||||
static const struct regmap_config imx_gpc_regmap_config = {
|
||||
.reg_bits = 32,
|
||||
.val_bits = 32,
|
||||
.reg_stride = 4,
|
||||
.max_register = 0x2ac,
|
||||
};
|
||||
|
||||
static struct generic_pm_domain *imx_gpc_onecell_domains[] = {
|
||||
&imx_gpc_domains[0].base,
|
||||
&imx_gpc_domains[1].base,
|
||||
};
|
||||
|
||||
static struct genpd_onecell_data imx_gpc_onecell_data = {
|
||||
.domains = imx_gpc_onecell_domains,
|
||||
.num_domains = 2,
|
||||
};
|
||||
|
||||
static int imx_gpc_old_dt_init(struct device *dev, struct regmap *regmap,
|
||||
unsigned int num_domains)
|
||||
{
|
||||
struct imx_pm_domain *domain;
|
||||
int i, ret;
|
||||
|
||||
for (i = 0; i < num_domains; i++) {
|
||||
domain = &imx_gpc_domains[i];
|
||||
domain->regmap = regmap;
|
||||
domain->ipg_rate_mhz = 66;
|
||||
|
||||
if (i == 1) {
|
||||
domain->supply = devm_regulator_get(dev, "pu");
|
||||
if (IS_ERR(domain->supply))
|
||||
return PTR_ERR(domain->supply);;
|
||||
|
||||
ret = imx_pgc_get_clocks(dev, domain);
|
||||
if (ret)
|
||||
goto clk_err;
|
||||
|
||||
domain->base.power_on(&domain->base);
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < num_domains; i++)
|
||||
pm_genpd_init(&imx_gpc_domains[i].base, NULL, false);
|
||||
|
||||
if (IS_ENABLED(CONFIG_PM_GENERIC_DOMAINS)) {
|
||||
ret = of_genpd_add_provider_onecell(dev->of_node,
|
||||
&imx_gpc_onecell_data);
|
||||
if (ret)
|
||||
goto genpd_err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
genpd_err:
|
||||
for (i = 0; i < num_domains; i++)
|
||||
pm_genpd_remove(&imx_gpc_domains[i].base);
|
||||
imx_pgc_put_clocks(&imx_gpc_domains[GPC_PGC_DOMAIN_PU]);
|
||||
clk_err:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int imx_gpc_probe(struct platform_device *pdev)
|
||||
{
|
||||
const struct of_device_id *of_id =
|
||||
of_match_device(imx_gpc_dt_ids, &pdev->dev);
|
||||
const struct imx_gpc_dt_data *of_id_data = of_id->data;
|
||||
struct device_node *pgc_node;
|
||||
struct regmap *regmap;
|
||||
struct resource *res;
|
||||
void __iomem *base;
|
||||
int ret;
|
||||
|
||||
pgc_node = of_get_child_by_name(pdev->dev.of_node, "pgc");
|
||||
|
||||
/* bail out if DT too old and doesn't provide the necessary info */
|
||||
if (!of_property_read_bool(pdev->dev.of_node, "#power-domain-cells") &&
|
||||
!pgc_node)
|
||||
return 0;
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
base = devm_ioremap_resource(&pdev->dev, res);
|
||||
if (IS_ERR(base))
|
||||
return PTR_ERR(base);
|
||||
|
||||
regmap = devm_regmap_init_mmio_clk(&pdev->dev, NULL, base,
|
||||
&imx_gpc_regmap_config);
|
||||
if (IS_ERR(regmap)) {
|
||||
ret = PTR_ERR(regmap);
|
||||
dev_err(&pdev->dev, "failed to init regmap: %d\n",
|
||||
ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Disable PU power down in normal operation if ERR009619 is present */
|
||||
if (of_id_data->err009619_present)
|
||||
imx_gpc_domains[GPC_PGC_DOMAIN_PU].flags |=
|
||||
PGC_DOMAIN_FLAG_NO_PD;
|
||||
|
||||
if (!pgc_node) {
|
||||
ret = imx_gpc_old_dt_init(&pdev->dev, regmap,
|
||||
of_id_data->num_domains);
|
||||
if (ret)
|
||||
return ret;
|
||||
} else {
|
||||
struct imx_pm_domain *domain;
|
||||
struct platform_device *pd_pdev;
|
||||
struct device_node *np;
|
||||
struct clk *ipg_clk;
|
||||
unsigned int ipg_rate_mhz;
|
||||
int domain_index;
|
||||
|
||||
ipg_clk = devm_clk_get(&pdev->dev, "ipg");
|
||||
if (IS_ERR(ipg_clk))
|
||||
return PTR_ERR(ipg_clk);
|
||||
ipg_rate_mhz = clk_get_rate(ipg_clk) / 1000000;
|
||||
|
||||
for_each_child_of_node(pgc_node, np) {
|
||||
ret = of_property_read_u32(np, "reg", &domain_index);
|
||||
if (ret) {
|
||||
of_node_put(np);
|
||||
return ret;
|
||||
}
|
||||
if (domain_index >= of_id_data->num_domains)
|
||||
continue;
|
||||
|
||||
domain = &imx_gpc_domains[domain_index];
|
||||
domain->regmap = regmap;
|
||||
domain->ipg_rate_mhz = ipg_rate_mhz;
|
||||
|
||||
pd_pdev = platform_device_alloc("imx-pgc-power-domain",
|
||||
domain_index);
|
||||
if (!pd_pdev) {
|
||||
of_node_put(np);
|
||||
return -ENOMEM;
|
||||
}
|
||||
pd_pdev->dev.platform_data = domain;
|
||||
pd_pdev->dev.parent = &pdev->dev;
|
||||
pd_pdev->dev.of_node = np;
|
||||
|
||||
ret = platform_device_add(pd_pdev);
|
||||
if (ret) {
|
||||
platform_device_put(pd_pdev);
|
||||
of_node_put(np);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int imx_gpc_remove(struct platform_device *pdev)
|
||||
{
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* If the old DT binding is used the toplevel driver needs to
|
||||
* de-register the power domains
|
||||
*/
|
||||
if (!of_get_child_by_name(pdev->dev.of_node, "pgc")) {
|
||||
of_genpd_del_provider(pdev->dev.of_node);
|
||||
|
||||
ret = pm_genpd_remove(&imx_gpc_domains[GPC_PGC_DOMAIN_PU].base);
|
||||
if (ret)
|
||||
return ret;
|
||||
imx_pgc_put_clocks(&imx_gpc_domains[GPC_PGC_DOMAIN_PU]);
|
||||
|
||||
ret = pm_genpd_remove(&imx_gpc_domains[GPC_PGC_DOMAIN_ARM].base);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver imx_gpc_driver = {
|
||||
.driver = {
|
||||
.name = "imx-gpc",
|
||||
.of_match_table = imx_gpc_dt_ids,
|
||||
},
|
||||
.probe = imx_gpc_probe,
|
||||
.remove = imx_gpc_remove,
|
||||
};
|
||||
builtin_platform_driver(imx_gpc_driver)
|
363
drivers/soc/imx/gpcv2.c
Normal file
363
drivers/soc/imx/gpcv2.c
Normal file
|
@ -0,0 +1,363 @@
|
|||
/*
|
||||
* Copyright 2017 Impinj, Inc
|
||||
* Author: Andrey Smirnov <andrew.smirnov@gmail.com>
|
||||
*
|
||||
* Based on the code of analogus driver:
|
||||
*
|
||||
* Copyright 2015-2017 Pengutronix, Lucas Stach <kernel@pengutronix.de>
|
||||
*
|
||||
* The code contained herein is licensed under the GNU General Public
|
||||
* License. You may obtain a copy of the GNU General Public License
|
||||
* Version 2 or later at the following locations:
|
||||
*
|
||||
* http://www.opensource.org/licenses/gpl-license.html
|
||||
* http://www.gnu.org/copyleft/gpl.html
|
||||
*/
|
||||
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pm_domain.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <dt-bindings/power/imx7-power.h>
|
||||
|
||||
#define GPC_LPCR_A7_BSC 0x000
|
||||
|
||||
#define GPC_PGC_CPU_MAPPING 0x0ec
|
||||
#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 0x0f8
|
||||
#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)
|
||||
|
||||
#define GPC_M4_PU_PDN_FLG 0x1bc
|
||||
|
||||
|
||||
#define PGC_MIPI 4
|
||||
#define PGC_PCIE 5
|
||||
#define PGC_USB_HSIC 8
|
||||
#define GPC_PGC_CTRL(n) (0x800 + (n) * 0x40)
|
||||
#define GPC_PGC_SR(n) (GPC_PGC_CTRL(n) + 0xc)
|
||||
|
||||
#define GPC_PGC_CTRL_PCR BIT(0)
|
||||
|
||||
struct imx7_pgc_domain {
|
||||
struct generic_pm_domain genpd;
|
||||
struct regmap *regmap;
|
||||
struct regulator *regulator;
|
||||
|
||||
unsigned int pgc;
|
||||
|
||||
const struct {
|
||||
u32 pxx;
|
||||
u32 map;
|
||||
} bits;
|
||||
|
||||
const int voltage;
|
||||
struct device *dev;
|
||||
};
|
||||
|
||||
static int imx7_gpc_pu_pgc_sw_pxx_req(struct generic_pm_domain *genpd,
|
||||
bool on)
|
||||
{
|
||||
struct imx7_pgc_domain *domain = container_of(genpd,
|
||||
struct imx7_pgc_domain,
|
||||
genpd);
|
||||
unsigned int offset = on ?
|
||||
GPC_PU_PGC_SW_PUP_REQ : GPC_PU_PGC_SW_PDN_REQ;
|
||||
const bool enable_power_control = !on;
|
||||
const bool has_regulator = !IS_ERR(domain->regulator);
|
||||
unsigned long deadline;
|
||||
int ret = 0;
|
||||
|
||||
regmap_update_bits(domain->regmap, GPC_PGC_CPU_MAPPING,
|
||||
domain->bits.map, domain->bits.map);
|
||||
|
||||
if (has_regulator && on) {
|
||||
ret = regulator_enable(domain->regulator);
|
||||
if (ret) {
|
||||
dev_err(domain->dev, "failed to enable regulator\n");
|
||||
goto unmap;
|
||||
}
|
||||
}
|
||||
|
||||
if (enable_power_control)
|
||||
regmap_update_bits(domain->regmap, GPC_PGC_CTRL(domain->pgc),
|
||||
GPC_PGC_CTRL_PCR, GPC_PGC_CTRL_PCR);
|
||||
|
||||
regmap_update_bits(domain->regmap, offset,
|
||||
domain->bits.pxx, domain->bits.pxx);
|
||||
|
||||
/*
|
||||
* 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) {
|
||||
u32 pxx_req;
|
||||
|
||||
regmap_read(domain->regmap, offset, &pxx_req);
|
||||
|
||||
if (!(pxx_req & domain->bits.pxx))
|
||||
break;
|
||||
|
||||
if (time_after(jiffies, deadline)) {
|
||||
dev_err(domain->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 (enable_power_control)
|
||||
regmap_update_bits(domain->regmap, GPC_PGC_CTRL(domain->pgc),
|
||||
GPC_PGC_CTRL_PCR, 0);
|
||||
|
||||
if (has_regulator && !on) {
|
||||
int err;
|
||||
|
||||
err = regulator_disable(domain->regulator);
|
||||
if (err)
|
||||
dev_err(domain->dev,
|
||||
"failed to disable regulator: %d\n", ret);
|
||||
/* Preserve earlier error code */
|
||||
ret = ret ?: err;
|
||||
}
|
||||
unmap:
|
||||
regmap_update_bits(domain->regmap, GPC_PGC_CPU_MAPPING,
|
||||
domain->bits.map, 0);
|
||||
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);
|
||||
}
|
||||
|
||||
static struct imx7_pgc_domain imx7_pgc_domains[] = {
|
||||
[IMX7_POWER_DOMAIN_MIPI_PHY] = {
|
||||
.genpd = {
|
||||
.name = "mipi-phy",
|
||||
},
|
||||
.bits = {
|
||||
.pxx = MIPI_PHY_SW_Pxx_REQ,
|
||||
.map = MIPI_PHY_A7_DOMAIN,
|
||||
},
|
||||
.voltage = 1000000,
|
||||
.pgc = PGC_MIPI,
|
||||
},
|
||||
|
||||
[IMX7_POWER_DOMAIN_PCIE_PHY] = {
|
||||
.genpd = {
|
||||
.name = "pcie-phy",
|
||||
},
|
||||
.bits = {
|
||||
.pxx = PCIE_PHY_SW_Pxx_REQ,
|
||||
.map = PCIE_PHY_A7_DOMAIN,
|
||||
},
|
||||
.voltage = 1000000,
|
||||
.pgc = PGC_PCIE,
|
||||
},
|
||||
|
||||
[IMX7_POWER_DOMAIN_USB_HSIC_PHY] = {
|
||||
.genpd = {
|
||||
.name = "usb-hsic-phy",
|
||||
},
|
||||
.bits = {
|
||||
.pxx = USB_HSIC_PHY_SW_Pxx_REQ,
|
||||
.map = USB_HSIC_PHY_A7_DOMAIN,
|
||||
},
|
||||
.voltage = 1200000,
|
||||
.pgc = PGC_USB_HSIC,
|
||||
},
|
||||
};
|
||||
|
||||
static int imx7_pgc_domain_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct imx7_pgc_domain *domain = pdev->dev.platform_data;
|
||||
int ret;
|
||||
|
||||
domain->dev = &pdev->dev;
|
||||
|
||||
ret = pm_genpd_init(&domain->genpd, NULL, true);
|
||||
if (ret) {
|
||||
dev_err(domain->dev, "Failed to init power domain\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
domain->regulator = devm_regulator_get_optional(domain->dev, "power");
|
||||
if (IS_ERR(domain->regulator)) {
|
||||
if (PTR_ERR(domain->regulator) != -ENODEV) {
|
||||
dev_err(domain->dev, "Failed to get domain's regulator\n");
|
||||
return PTR_ERR(domain->regulator);
|
||||
}
|
||||
} else {
|
||||
regulator_set_voltage(domain->regulator,
|
||||
domain->voltage, domain->voltage);
|
||||
}
|
||||
|
||||
ret = of_genpd_add_provider_simple(domain->dev->of_node,
|
||||
&domain->genpd);
|
||||
if (ret) {
|
||||
dev_err(domain->dev, "Failed to add genpd provider\n");
|
||||
pm_genpd_remove(&domain->genpd);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int imx7_pgc_domain_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct imx7_pgc_domain *domain = pdev->dev.platform_data;
|
||||
|
||||
of_genpd_del_provider(domain->dev->of_node);
|
||||
pm_genpd_remove(&domain->genpd);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct platform_device_id imx7_pgc_domain_id[] = {
|
||||
{ "imx7-pgc-domain", },
|
||||
{ },
|
||||
};
|
||||
|
||||
static struct platform_driver imx7_pgc_domain_driver = {
|
||||
.driver = {
|
||||
.name = "imx7-pgc",
|
||||
},
|
||||
.probe = imx7_pgc_domain_probe,
|
||||
.remove = imx7_pgc_domain_remove,
|
||||
.id_table = imx7_pgc_domain_id,
|
||||
};
|
||||
builtin_platform_driver(imx7_pgc_domain_driver)
|
||||
|
||||
static int imx_gpcv2_probe(struct platform_device *pdev)
|
||||
{
|
||||
static const struct regmap_range yes_ranges[] = {
|
||||
regmap_reg_range(GPC_LPCR_A7_BSC,
|
||||
GPC_M4_PU_PDN_FLG),
|
||||
regmap_reg_range(GPC_PGC_CTRL(PGC_MIPI),
|
||||
GPC_PGC_SR(PGC_MIPI)),
|
||||
regmap_reg_range(GPC_PGC_CTRL(PGC_PCIE),
|
||||
GPC_PGC_SR(PGC_PCIE)),
|
||||
regmap_reg_range(GPC_PGC_CTRL(PGC_USB_HSIC),
|
||||
GPC_PGC_SR(PGC_USB_HSIC)),
|
||||
};
|
||||
static const struct regmap_access_table access_table = {
|
||||
.yes_ranges = yes_ranges,
|
||||
.n_yes_ranges = ARRAY_SIZE(yes_ranges),
|
||||
};
|
||||
static const struct regmap_config regmap_config = {
|
||||
.reg_bits = 32,
|
||||
.val_bits = 32,
|
||||
.reg_stride = 4,
|
||||
.rd_table = &access_table,
|
||||
.wr_table = &access_table,
|
||||
.max_register = SZ_4K,
|
||||
};
|
||||
struct device *dev = &pdev->dev;
|
||||
struct device_node *pgc_np, *np;
|
||||
struct regmap *regmap;
|
||||
struct resource *res;
|
||||
void __iomem *base;
|
||||
int ret;
|
||||
|
||||
pgc_np = of_get_child_by_name(dev->of_node, "pgc");
|
||||
if (!pgc_np) {
|
||||
dev_err(dev, "No power domains specified in DT\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
base = devm_ioremap_resource(dev, res);
|
||||
if (IS_ERR(base))
|
||||
return PTR_ERR(base);
|
||||
|
||||
regmap = devm_regmap_init_mmio(dev, base, ®map_config);
|
||||
if (IS_ERR(regmap)) {
|
||||
ret = PTR_ERR(regmap);
|
||||
dev_err(dev, "failed to init regmap (%d)\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
for_each_child_of_node(pgc_np, np) {
|
||||
struct platform_device *pd_pdev;
|
||||
struct imx7_pgc_domain *domain;
|
||||
u32 domain_index;
|
||||
|
||||
ret = of_property_read_u32(np, "reg", &domain_index);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to read 'reg' property\n");
|
||||
of_node_put(np);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (domain_index >= ARRAY_SIZE(imx7_pgc_domains)) {
|
||||
dev_warn(dev,
|
||||
"Domain index %d is out of bounds\n",
|
||||
domain_index);
|
||||
continue;
|
||||
}
|
||||
|
||||
domain = &imx7_pgc_domains[domain_index];
|
||||
domain->regmap = regmap;
|
||||
domain->genpd.power_on = imx7_gpc_pu_pgc_sw_pup_req;
|
||||
domain->genpd.power_off = imx7_gpc_pu_pgc_sw_pdn_req;
|
||||
|
||||
pd_pdev = platform_device_alloc("imx7-pgc-domain",
|
||||
domain_index);
|
||||
if (!pd_pdev) {
|
||||
dev_err(dev, "Failed to allocate platform device\n");
|
||||
of_node_put(np);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
pd_pdev->dev.platform_data = domain;
|
||||
pd_pdev->dev.parent = dev;
|
||||
pd_pdev->dev.of_node = np;
|
||||
|
||||
ret = platform_device_add(pd_pdev);
|
||||
if (ret) {
|
||||
platform_device_put(pd_pdev);
|
||||
of_node_put(np);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id imx_gpcv2_dt_ids[] = {
|
||||
{ .compatible = "fsl,imx7d-gpc" },
|
||||
{ }
|
||||
};
|
||||
|
||||
static struct platform_driver imx_gpc_driver = {
|
||||
.driver = {
|
||||
.name = "imx-gpcv2",
|
||||
.of_match_table = imx_gpcv2_dt_ids,
|
||||
},
|
||||
.probe = imx_gpcv2_probe,
|
||||
};
|
||||
builtin_platform_driver(imx_gpc_driver)
|
16
include/dt-bindings/power/imx7-power.h
Normal file
16
include/dt-bindings/power/imx7-power.h
Normal file
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* Copyright (C) 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_IMX7_POWER_H__
|
||||
#define __DT_BINDINGS_IMX7_POWER_H__
|
||||
|
||||
#define IMX7_POWER_DOMAIN_MIPI_PHY 0
|
||||
#define IMX7_POWER_DOMAIN_PCIE_PHY 1
|
||||
#define IMX7_POWER_DOMAIN_USB_HSIC_PHY 2
|
||||
|
||||
#endif
|
Loading…
Reference in New Issue
Block a user