aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/soc
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2017-05-09 10:01:15 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2017-05-09 10:01:15 -0700
commit0160e00ae8e987be8822745fb166aa76451c9bcc (patch)
treedeca2d09a729155ed0cb631f2bc8f557e634ab06 /drivers/soc
parentMerge tag 'armsoc-defconfig' of git://git.kernel.org/pub/scm/linux/kernel/git/arm/arm-soc (diff)
parentMerge branch 'for_4.12/soc-pmdomain-p2' of git://git.kernel.org/pub/scm/linux/kernel/git/ssantosh/linux-keystone into next/drivers (diff)
downloadlinux-dev-0160e00ae8e987be8822745fb166aa76451c9bcc.tar.xz
linux-dev-0160e00ae8e987be8822745fb166aa76451c9bcc.zip
Merge tag 'armsoc-drivers' of git://git.kernel.org/pub/scm/linux/kernel/git/arm/arm-soc
Pull ARM SoC driver updates from Olof Johansson: "Driver updates for ARM SoCs: Reset subsystem, merged through arm-soc by tradition: - Make bool drivers explicitly non-modular - New support for i.MX7 and Arria10 reset controllers PATA driver for Palmchip BK371 (acked by Tejun) Power domain drivers for i.MX (GPC, GPCv2) - Moved out of mach-imx for GPC - Bunch of tweaks, fixes, etc PMC support for Tegra186 SoC detection support for Renesas RZ/G1H and RZ/G1N Move Tegra flow controller driver from mach directory to drivers/soc - (Power management / CPU power driver) Misc smaller tweaks for other platforms" * tag 'armsoc-drivers' of git://git.kernel.org/pub/scm/linux/kernel/git/arm/arm-soc: (60 commits) soc: pm-domain: Fix the mangled urls soc: renesas: rcar-sysc: Add support for R-Car H3 ES2.0 soc: renesas: rcar-sysc: Add support for fixing up power area tables soc: renesas: Register SoC device early 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 ARM/clk: move the ICST library to drivers/clk ARM: plat-versatile: remove stale clock header ARM: keystone: Drop PM domain support for k2g soc: ti: Add ti_sci_pm_domains driver dt-bindings: Add TI SCI PM Domains PM / Domains: Do not check if simple providers have phandle cells PM / Domains: Add generic data pointer to genpd data struct soc/tegra: Add initial flowctrl support for Tegra132/210 soc/tegra: flowctrl: Add basic platform driver soc/tegra: Move Tegra flowctrl driver ARM: tegra: Remove unnecessary inclusion of flowctrl header ...
Diffstat (limited to 'drivers/soc')
-rw-r--r--drivers/soc/Kconfig1
-rw-r--r--drivers/soc/Makefile1
-rw-r--r--drivers/soc/bcm/brcmstb/common.c9
-rw-r--r--drivers/soc/imx/Kconfig9
-rw-r--r--drivers/soc/imx/Makefile2
-rw-r--r--drivers/soc/imx/gpc.c489
-rw-r--r--drivers/soc/imx/gpcv2.c363
-rw-r--r--drivers/soc/renesas/r8a7795-sysc.c26
-rw-r--r--drivers/soc/renesas/rcar-sysc.c25
-rw-r--r--drivers/soc/renesas/rcar-sysc.h10
-rw-r--r--drivers/soc/renesas/renesas-soc.c18
-rw-r--r--drivers/soc/samsung/Kconfig8
-rw-r--r--drivers/soc/samsung/Makefile4
-rw-r--r--drivers/soc/samsung/exynos-pmu.c22
-rw-r--r--drivers/soc/samsung/exynos-pmu.h3
-rw-r--r--drivers/soc/tegra/Kconfig22
-rw-r--r--drivers/soc/tegra/Makefile4
-rw-r--r--drivers/soc/tegra/flowctrl.c224
-rw-r--r--drivers/soc/tegra/fuse/fuse-tegra.c4
-rw-r--r--drivers/soc/tegra/pmc-tegra186.c169
-rw-r--r--drivers/soc/ti/Kconfig12
-rw-r--r--drivers/soc/ti/Makefile1
-rw-r--r--drivers/soc/ti/ti_sci_pm_domains.c202
-rw-r--r--drivers/soc/zte/zx296718_pm_domains.c1
-rw-r--r--drivers/soc/zte/zx2967_pm_domains.c4
25 files changed, 1614 insertions, 19 deletions
diff --git a/drivers/soc/Kconfig b/drivers/soc/Kconfig
index 45e5b13a3c02..309643fe35f9 100644
--- a/drivers/soc/Kconfig
+++ b/drivers/soc/Kconfig
@@ -3,6 +3,7 @@ menu "SOC (System On Chip) specific Drivers"
source "drivers/soc/atmel/Kconfig"
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"
diff --git a/drivers/soc/Makefile b/drivers/soc/Makefile
index 3467de7d3890..824b44281efa 100644
--- a/drivers/soc/Makefile
+++ b/drivers/soc/Makefile
@@ -7,6 +7,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/
diff --git a/drivers/soc/bcm/brcmstb/common.c b/drivers/soc/bcm/brcmstb/common.c
index 94e7335553f4..b6195fdf0d00 100644
--- a/drivers/soc/bcm/brcmstb/common.c
+++ b/drivers/soc/bcm/brcmstb/common.c
@@ -41,6 +41,15 @@ bool soc_is_brcmstb(void)
}
static const struct of_device_id sun_top_ctrl_match[] = {
+ { .compatible = "brcm,bcm7125-sun-top-ctrl", },
+ { .compatible = "brcm,bcm7346-sun-top-ctrl", },
+ { .compatible = "brcm,bcm7358-sun-top-ctrl", },
+ { .compatible = "brcm,bcm7360-sun-top-ctrl", },
+ { .compatible = "brcm,bcm7362-sun-top-ctrl", },
+ { .compatible = "brcm,bcm7420-sun-top-ctrl", },
+ { .compatible = "brcm,bcm7425-sun-top-ctrl", },
+ { .compatible = "brcm,bcm7429-sun-top-ctrl", },
+ { .compatible = "brcm,bcm7425-sun-top-ctrl", },
{ .compatible = "brcm,brcmstb-sun-top-ctrl", },
{ }
};
diff --git a/drivers/soc/imx/Kconfig b/drivers/soc/imx/Kconfig
new file mode 100644
index 000000000000..357a5d8f8da0
--- /dev/null
+++ b/drivers/soc/imx/Kconfig
@@ -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
diff --git a/drivers/soc/imx/Makefile b/drivers/soc/imx/Makefile
new file mode 100644
index 000000000000..5b6e396c1121
--- /dev/null
+++ b/drivers/soc/imx/Makefile
@@ -0,0 +1,2 @@
+obj-y += gpc.o
+obj-$(CONFIG_IMX7_PM_DOMAINS) += gpcv2.o
diff --git a/drivers/soc/imx/gpc.c b/drivers/soc/imx/gpc.c
new file mode 100644
index 000000000000..47e7aa963dbb
--- /dev/null
+++ b/drivers/soc/imx/gpc.c
@@ -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)
diff --git a/drivers/soc/imx/gpcv2.c b/drivers/soc/imx/gpcv2.c
new file mode 100644
index 000000000000..3039072911a5
--- /dev/null
+++ b/drivers/soc/imx/gpcv2.c
@@ -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, &regmap_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)
diff --git a/drivers/soc/renesas/r8a7795-sysc.c b/drivers/soc/renesas/r8a7795-sysc.c
index 5e7537c96f7b..7412666187b3 100644
--- a/drivers/soc/renesas/r8a7795-sysc.c
+++ b/drivers/soc/renesas/r8a7795-sysc.c
@@ -1,7 +1,7 @@
/*
* Renesas R-Car H3 System Controller
*
- * Copyright (C) 2016 Glider bvba
+ * Copyright (C) 2016-2017 Glider bvba
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -10,12 +10,13 @@
#include <linux/bug.h>
#include <linux/kernel.h>
+#include <linux/sys_soc.h>
#include <dt-bindings/power/r8a7795-sysc.h>
#include "rcar-sysc.h"
-static const struct rcar_sysc_area r8a7795_areas[] __initconst = {
+static struct rcar_sysc_area r8a7795_areas[] __initdata = {
{ "always-on", 0, 0, R8A7795_PD_ALWAYS_ON, -1, PD_ALWAYS_ON },
{ "ca57-scu", 0x1c0, 0, R8A7795_PD_CA57_SCU, R8A7795_PD_ALWAYS_ON,
PD_SCU },
@@ -40,6 +41,7 @@ static const struct rcar_sysc_area r8a7795_areas[] __initconst = {
{ "a3vp", 0x340, 0, R8A7795_PD_A3VP, R8A7795_PD_ALWAYS_ON },
{ "cr7", 0x240, 0, R8A7795_PD_CR7, R8A7795_PD_ALWAYS_ON },
{ "a3vc", 0x380, 0, R8A7795_PD_A3VC, R8A7795_PD_ALWAYS_ON },
+ /* A2VC0 exists on ES1.x only */
{ "a2vc0", 0x3c0, 0, R8A7795_PD_A2VC0, R8A7795_PD_A3VC },
{ "a2vc1", 0x3c0, 1, R8A7795_PD_A2VC1, R8A7795_PD_A3VC },
{ "3dg-a", 0x100, 0, R8A7795_PD_3DG_A, R8A7795_PD_ALWAYS_ON },
@@ -50,7 +52,27 @@ static const struct rcar_sysc_area r8a7795_areas[] __initconst = {
{ "a3ir", 0x180, 0, R8A7795_PD_A3IR, R8A7795_PD_ALWAYS_ON },
};
+
+ /*
+ * Fixups for R-Car H3 revisions after ES1.x
+ */
+
+static const struct soc_device_attribute r8a7795es1[] __initconst = {
+ { .soc_id = "r8a7795", .revision = "ES1.*" },
+ { /* sentinel */ }
+};
+
+static int __init r8a7795_sysc_init(void)
+{
+ if (!soc_device_match(r8a7795es1))
+ rcar_sysc_nullify(r8a7795_areas, ARRAY_SIZE(r8a7795_areas),
+ R8A7795_PD_A2VC0);
+
+ return 0;
+}
+
const struct rcar_sysc_info r8a7795_sysc_info __initconst = {
+ .init = r8a7795_sysc_init,
.areas = r8a7795_areas,
.num_areas = ARRAY_SIZE(r8a7795_areas),
};
diff --git a/drivers/soc/renesas/rcar-sysc.c b/drivers/soc/renesas/rcar-sysc.c
index 225c35c79d9a..528a13742aeb 100644
--- a/drivers/soc/renesas/rcar-sysc.c
+++ b/drivers/soc/renesas/rcar-sysc.c
@@ -2,7 +2,7 @@
* R-Car SYSC Power management support
*
* Copyright (C) 2014 Magnus Damm
- * Copyright (C) 2015-2016 Glider bvba
+ * Copyright (C) 2015-2017 Glider bvba
*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file "COPYING" in the main directory of this archive
@@ -334,6 +334,12 @@ static int __init rcar_sysc_pd_init(void)
info = match->data;
+ if (info->init) {
+ error = info->init();
+ if (error)
+ return error;
+ }
+
has_cpg_mstp = of_find_compatible_node(NULL, NULL,
"renesas,cpg-mstp-clocks");
@@ -377,6 +383,11 @@ static int __init rcar_sysc_pd_init(void)
const struct rcar_sysc_area *area = &info->areas[i];
struct rcar_sysc_pd *pd;
+ if (!area->name) {
+ /* Skip NULLified area */
+ continue;
+ }
+
pd = kzalloc(sizeof(*pd) + strlen(area->name) + 1, GFP_KERNEL);
if (!pd) {
error = -ENOMEM;
@@ -406,6 +417,18 @@ out_put:
}
early_initcall(rcar_sysc_pd_init);
+void __init rcar_sysc_nullify(struct rcar_sysc_area *areas,
+ unsigned int num_areas, u8 id)
+{
+ unsigned int i;
+
+ for (i = 0; i < num_areas; i++)
+ if (areas[i].isr_bit == id) {
+ areas[i].name = NULL;
+ return;
+ }
+}
+
void __init rcar_sysc_init(phys_addr_t base, u32 syscier)
{
u32 syscimr;
diff --git a/drivers/soc/renesas/rcar-sysc.h b/drivers/soc/renesas/rcar-sysc.h
index f6e842e2976e..07edb049a401 100644
--- a/drivers/soc/renesas/rcar-sysc.h
+++ b/drivers/soc/renesas/rcar-sysc.h
@@ -46,6 +46,7 @@ struct rcar_sysc_area {
*/
struct rcar_sysc_info {
+ int (*init)(void); /* Optional */
const struct rcar_sysc_area *areas;
unsigned int num_areas;
};
@@ -59,4 +60,13 @@ extern const struct rcar_sysc_info r8a7792_sysc_info;
extern const struct rcar_sysc_info r8a7794_sysc_info;
extern const struct rcar_sysc_info r8a7795_sysc_info;
extern const struct rcar_sysc_info r8a7796_sysc_info;
+
+
+ /*
+ * Helpers for fixing up power area tables depending on SoC revision
+ */
+
+extern void rcar_sysc_nullify(struct rcar_sysc_area *areas,
+ unsigned int num_areas, u8 id);
+
#endif /* __SOC_RENESAS_RCAR_SYSC_H__ */
diff --git a/drivers/soc/renesas/renesas-soc.c b/drivers/soc/renesas/renesas-soc.c
index 330960312296..ca26f13d399c 100644
--- a/drivers/soc/renesas/renesas-soc.c
+++ b/drivers/soc/renesas/renesas-soc.c
@@ -80,11 +80,21 @@ static const struct renesas_soc soc_rmobile_a1 __initconst __maybe_unused = {
.id = 0x40,
};
+static const struct renesas_soc soc_rz_g1h __initconst __maybe_unused = {
+ .family = &fam_rzg,
+ .id = 0x45,
+};
+
static const struct renesas_soc soc_rz_g1m __initconst __maybe_unused = {
.family = &fam_rzg,
.id = 0x47,
};
+static const struct renesas_soc soc_rz_g1n __initconst __maybe_unused = {
+ .family = &fam_rzg,
+ .id = 0x4b,
+};
+
static const struct renesas_soc soc_rz_g1e __initconst __maybe_unused = {
.family = &fam_rzg,
.id = 0x4c,
@@ -150,9 +160,15 @@ static const struct of_device_id renesas_socs[] __initconst = {
#ifdef CONFIG_ARCH_R8A7740
{ .compatible = "renesas,r8a7740", .data = &soc_rmobile_a1 },
#endif
+#ifdef CONFIG_ARCH_R8A7742
+ { .compatible = "renesas,r8a7742", .data = &soc_rz_g1h },
+#endif
#ifdef CONFIG_ARCH_R8A7743
{ .compatible = "renesas,r8a7743", .data = &soc_rz_g1m },
#endif
+#ifdef CONFIG_ARCH_R8A7744
+ { .compatible = "renesas,r8a7744", .data = &soc_rz_g1n },
+#endif
#ifdef CONFIG_ARCH_R8A7745
{ .compatible = "renesas,r8a7745", .data = &soc_rz_g1e },
#endif
@@ -254,4 +270,4 @@ static int __init renesas_soc_init(void)
return 0;
}
-core_initcall(renesas_soc_init);
+early_initcall(renesas_soc_init);
diff --git a/drivers/soc/samsung/Kconfig b/drivers/soc/samsung/Kconfig
index 245533907d1b..8b25bd55e648 100644
--- a/drivers/soc/samsung/Kconfig
+++ b/drivers/soc/samsung/Kconfig
@@ -8,7 +8,13 @@ if SOC_SAMSUNG
config EXYNOS_PMU
bool "Exynos PMU controller driver" if COMPILE_TEST
- depends on (ARM && ARCH_EXYNOS) || ((ARM || ARM64) && COMPILE_TEST)
+ depends on ARCH_EXYNOS || ((ARM || ARM64) && COMPILE_TEST)
+ select EXYNOS_PMU_ARM_DRIVERS if ARM && ARCH_EXYNOS
+
+# There is no need to enable these drivers for ARMv8
+config EXYNOS_PMU_ARM_DRIVERS
+ bool "Exynos PMU ARMv7-specific driver extensions" if COMPILE_TEST
+ depends on EXYNOS_PMU
config EXYNOS_PM_DOMAINS
bool "Exynos PM domains" if COMPILE_TEST
diff --git a/drivers/soc/samsung/Makefile b/drivers/soc/samsung/Makefile
index 3619f2ecddaa..4d7694a4e7a4 100644
--- a/drivers/soc/samsung/Makefile
+++ b/drivers/soc/samsung/Makefile
@@ -1,3 +1,5 @@
-obj-$(CONFIG_EXYNOS_PMU) += exynos-pmu.o exynos3250-pmu.o exynos4-pmu.o \
+obj-$(CONFIG_EXYNOS_PMU) += exynos-pmu.o
+
+obj-$(CONFIG_EXYNOS_PMU_ARM_DRIVERS) += exynos3250-pmu.o exynos4-pmu.o \
exynos5250-pmu.o exynos5420-pmu.o
obj-$(CONFIG_EXYNOS_PM_DOMAINS) += pm_domains.o
diff --git a/drivers/soc/samsung/exynos-pmu.c b/drivers/soc/samsung/exynos-pmu.c
index 56d9244ff981..bd4a76f27bc2 100644
--- a/drivers/soc/samsung/exynos-pmu.c
+++ b/drivers/soc/samsung/exynos-pmu.c
@@ -69,27 +69,37 @@ void exynos_sys_powerdown_conf(enum sys_powerdown mode)
}
/*
+ * Split the data between ARM architectures because it is relatively big
+ * and useless on other arch.
+ */
+#ifdef CONFIG_EXYNOS_PMU_ARM_DRIVERS
+#define exynos_pmu_data_arm_ptr(data) (&data)
+#else
+#define exynos_pmu_data_arm_ptr(data) NULL
+#endif
+
+/*
* PMU platform driver and devicetree bindings.
*/
static const struct of_device_id exynos_pmu_of_device_ids[] = {
{
.compatible = "samsung,exynos3250-pmu",
- .data = &exynos3250_pmu_data,
+ .data = exynos_pmu_data_arm_ptr(exynos3250_pmu_data),
}, {
.compatible = "samsung,exynos4210-pmu",
- .data = &exynos4210_pmu_data,
+ .data = exynos_pmu_data_arm_ptr(exynos4210_pmu_data),
}, {
.compatible = "samsung,exynos4212-pmu",
- .data = &exynos4212_pmu_data,
+ .data = exynos_pmu_data_arm_ptr(exynos4212_pmu_data),
}, {
.compatible = "samsung,exynos4412-pmu",
- .data = &exynos4412_pmu_data,
+ .data = exynos_pmu_data_arm_ptr(exynos4412_pmu_data),
}, {
.compatible = "samsung,exynos5250-pmu",
- .data = &exynos5250_pmu_data,
+ .data = exynos_pmu_data_arm_ptr(exynos5250_pmu_data),
}, {
.compatible = "samsung,exynos5420-pmu",
- .data = &exynos5420_pmu_data,
+ .data = exynos_pmu_data_arm_ptr(exynos5420_pmu_data),
}, {
.compatible = "samsung,exynos5433-pmu",
},
diff --git a/drivers/soc/samsung/exynos-pmu.h b/drivers/soc/samsung/exynos-pmu.h
index a469e366fead..40d4229abfb5 100644
--- a/drivers/soc/samsung/exynos-pmu.h
+++ b/drivers/soc/samsung/exynos-pmu.h
@@ -31,6 +31,8 @@ struct exynos_pmu_data {
};
extern void __iomem *pmu_base_addr;
+
+#ifdef CONFIG_EXYNOS_PMU_ARM_DRIVERS
/* list of all exported SoC specific data */
extern const struct exynos_pmu_data exynos3250_pmu_data;
extern const struct exynos_pmu_data exynos4210_pmu_data;
@@ -38,6 +40,7 @@ extern const struct exynos_pmu_data exynos4212_pmu_data;
extern const struct exynos_pmu_data exynos4412_pmu_data;
extern const struct exynos_pmu_data exynos5250_pmu_data;
extern const struct exynos_pmu_data exynos5420_pmu_data;
+#endif
extern void pmu_raw_writel(u32 val, u32 offset);
extern u32 pmu_raw_readl(u32 offset);
diff --git a/drivers/soc/tegra/Kconfig b/drivers/soc/tegra/Kconfig
index e5e124c07066..dcf088db40b6 100644
--- a/drivers/soc/tegra/Kconfig
+++ b/drivers/soc/tegra/Kconfig
@@ -12,6 +12,8 @@ config ARCH_TEGRA_2x_SOC
select PINCTRL_TEGRA20
select PL310_ERRATA_727915 if CACHE_L2X0
select PL310_ERRATA_769419 if CACHE_L2X0
+ select SOC_TEGRA_FLOWCTRL
+ select SOC_TEGRA_PMC
select TEGRA_TIMER
help
Support for NVIDIA Tegra AP20 and T20 processors, based on the
@@ -23,6 +25,8 @@ config ARCH_TEGRA_3x_SOC
select ARM_ERRATA_764369 if SMP
select PINCTRL_TEGRA30
select PL310_ERRATA_769419 if CACHE_L2X0
+ select SOC_TEGRA_FLOWCTRL
+ select SOC_TEGRA_PMC
select TEGRA_TIMER
help
Support for NVIDIA Tegra T30 processor family, based on the
@@ -33,6 +37,8 @@ config ARCH_TEGRA_114_SOC
select ARM_ERRATA_798181 if SMP
select HAVE_ARM_ARCH_TIMER
select PINCTRL_TEGRA114
+ select SOC_TEGRA_FLOWCTRL
+ select SOC_TEGRA_PMC
select TEGRA_TIMER
help
Support for NVIDIA Tegra T114 processor family, based on the
@@ -42,6 +48,8 @@ config ARCH_TEGRA_124_SOC
bool "Enable support for Tegra124 family"
select HAVE_ARM_ARCH_TIMER
select PINCTRL_TEGRA124
+ select SOC_TEGRA_FLOWCTRL
+ select SOC_TEGRA_PMC
select TEGRA_TIMER
help
Support for NVIDIA Tegra T124 processor family, based on the
@@ -55,6 +63,8 @@ if ARM64
config ARCH_TEGRA_132_SOC
bool "NVIDIA Tegra132 SoC"
select PINCTRL_TEGRA124
+ select SOC_TEGRA_FLOWCTRL
+ select SOC_TEGRA_PMC
help
Enable support for NVIDIA Tegra132 SoC, based on the Denver
ARMv8 CPU. The Tegra132 SoC is similar to the Tegra124 SoC,
@@ -64,6 +74,8 @@ config ARCH_TEGRA_132_SOC
config ARCH_TEGRA_210_SOC
bool "NVIDIA Tegra210 SoC"
select PINCTRL_TEGRA210
+ select SOC_TEGRA_FLOWCTRL
+ select SOC_TEGRA_PMC
help
Enable support for the NVIDIA Tegra210 SoC. Also known as Tegra X1,
the Tegra210 has four Cortex-A57 cores paired with four Cortex-A53
@@ -83,6 +95,7 @@ config ARCH_TEGRA_186_SOC
select TEGRA_BPMP
select TEGRA_HSP_MBOX
select TEGRA_IVC
+ select SOC_TEGRA_PMC_TEGRA186
help
Enable support for the NVIDIA Tegar186 SoC. The Tegra186 features a
combination of Denver and Cortex-A57 CPU cores and a GPU based on
@@ -93,3 +106,12 @@ config ARCH_TEGRA_186_SOC
endif
endif
+
+config SOC_TEGRA_FLOWCTRL
+ bool
+
+config SOC_TEGRA_PMC
+ bool
+
+config SOC_TEGRA_PMC_TEGRA186
+ bool
diff --git a/drivers/soc/tegra/Makefile b/drivers/soc/tegra/Makefile
index ae857ff7d53d..4f81dd55e5d1 100644
--- a/drivers/soc/tegra/Makefile
+++ b/drivers/soc/tegra/Makefile
@@ -1,4 +1,6 @@
obj-y += fuse/
obj-y += common.o
-obj-y += pmc.o
+obj-$(CONFIG_SOC_TEGRA_FLOWCTRL) += flowctrl.o
+obj-$(CONFIG_SOC_TEGRA_PMC) += pmc.o
+obj-$(CONFIG_SOC_TEGRA_PMC_TEGRA186) += pmc-tegra186.o
diff --git a/drivers/soc/tegra/flowctrl.c b/drivers/soc/tegra/flowctrl.c
new file mode 100644
index 000000000000..0e345c05fc65
--- /dev/null
+++ b/drivers/soc/tegra/flowctrl.c
@@ -0,0 +1,224 @@
+/*
+ * drivers/soc/tegra/flowctrl.c
+ *
+ * Functions and macros to control the flowcontroller
+ *
+ * Copyright (c) 2010-2012, NVIDIA Corporation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/cpumask.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/platform_device.h>
+
+#include <soc/tegra/common.h>
+#include <soc/tegra/flowctrl.h>
+#include <soc/tegra/fuse.h>
+
+static u8 flowctrl_offset_halt_cpu[] = {
+ FLOW_CTRL_HALT_CPU0_EVENTS,
+ FLOW_CTRL_HALT_CPU1_EVENTS,
+ FLOW_CTRL_HALT_CPU1_EVENTS + 8,
+ FLOW_CTRL_HALT_CPU1_EVENTS + 16,
+};
+
+static u8 flowctrl_offset_cpu_csr[] = {
+ FLOW_CTRL_CPU0_CSR,
+ FLOW_CTRL_CPU1_CSR,
+ FLOW_CTRL_CPU1_CSR + 8,
+ FLOW_CTRL_CPU1_CSR + 16,
+};
+
+static void __iomem *tegra_flowctrl_base;
+
+static void flowctrl_update(u8 offset, u32 value)
+{
+ if (WARN_ONCE(IS_ERR_OR_NULL(tegra_flowctrl_base),
+ "Tegra flowctrl not initialised!\n"))
+ return;
+
+ writel(value, tegra_flowctrl_base + offset);
+
+ /* ensure the update has reached the flow controller */
+ wmb();
+ readl_relaxed(tegra_flowctrl_base + offset);
+}
+
+u32 flowctrl_read_cpu_csr(unsigned int cpuid)
+{
+ u8 offset = flowctrl_offset_cpu_csr[cpuid];
+
+ if (WARN_ONCE(IS_ERR_OR_NULL(tegra_flowctrl_base),
+ "Tegra flowctrl not initialised!\n"))
+ return 0;
+
+ return readl(tegra_flowctrl_base + offset);
+}
+
+void flowctrl_write_cpu_csr(unsigned int cpuid, u32 value)
+{
+ return flowctrl_update(flowctrl_offset_cpu_csr[cpuid], value);
+}
+
+void flowctrl_write_cpu_halt(unsigned int cpuid, u32 value)
+{
+ return flowctrl_update(flowctrl_offset_halt_cpu[cpuid], value);
+}
+
+void flowctrl_cpu_suspend_enter(unsigned int cpuid)
+{
+ unsigned int reg;
+ int i;
+
+ reg = flowctrl_read_cpu_csr(cpuid);
+ switch (tegra_get_chip_id()) {
+ case TEGRA20:
+ /* clear wfe bitmap */
+ reg &= ~TEGRA20_FLOW_CTRL_CSR_WFE_BITMAP;
+ /* clear wfi bitmap */
+ reg &= ~TEGRA20_FLOW_CTRL_CSR_WFI_BITMAP;
+ /* pwr gating on wfe */
+ reg |= TEGRA20_FLOW_CTRL_CSR_WFE_CPU0 << cpuid;
+ break;
+ case TEGRA30:
+ case TEGRA114:
+ case TEGRA124:
+ /* clear wfe bitmap */
+ reg &= ~TEGRA30_FLOW_CTRL_CSR_WFE_BITMAP;
+ /* clear wfi bitmap */
+ reg &= ~TEGRA30_FLOW_CTRL_CSR_WFI_BITMAP;
+ /* pwr gating on wfi */
+ reg |= TEGRA30_FLOW_CTRL_CSR_WFI_CPU0 << cpuid;
+ break;
+ }
+ reg |= FLOW_CTRL_CSR_INTR_FLAG; /* clear intr flag */
+ reg |= FLOW_CTRL_CSR_EVENT_FLAG; /* clear event flag */
+ reg |= FLOW_CTRL_CSR_ENABLE; /* pwr gating */
+ flowctrl_write_cpu_csr(cpuid, reg);
+
+ for (i = 0; i < num_possible_cpus(); i++) {
+ if (i == cpuid)
+ continue;
+ reg = flowctrl_read_cpu_csr(i);
+ reg |= FLOW_CTRL_CSR_EVENT_FLAG;
+ reg |= FLOW_CTRL_CSR_INTR_FLAG;
+ flowctrl_write_cpu_csr(i, reg);
+ }
+}
+
+void flowctrl_cpu_suspend_exit(unsigned int cpuid)
+{
+ unsigned int reg;
+
+ /* Disable powergating via flow controller for CPU0 */
+ reg = flowctrl_read_cpu_csr(cpuid);
+ switch (tegra_get_chip_id()) {
+ case TEGRA20:
+ /* clear wfe bitmap */
+ reg &= ~TEGRA20_FLOW_CTRL_CSR_WFE_BITMAP;
+ /* clear wfi bitmap */
+ reg &= ~TEGRA20_FLOW_CTRL_CSR_WFI_BITMAP;
+ break;
+ case TEGRA30:
+ case TEGRA114:
+ case TEGRA124:
+ /* clear wfe bitmap */
+ reg &= ~TEGRA30_FLOW_CTRL_CSR_WFE_BITMAP;
+ /* clear wfi bitmap */
+ reg &= ~TEGRA30_FLOW_CTRL_CSR_WFI_BITMAP;
+ break;
+ }
+ reg &= ~FLOW_CTRL_CSR_ENABLE; /* clear enable */
+ reg |= FLOW_CTRL_CSR_INTR_FLAG; /* clear intr */
+ reg |= FLOW_CTRL_CSR_EVENT_FLAG; /* clear event */
+ flowctrl_write_cpu_csr(cpuid, reg);
+}
+
+static int tegra_flowctrl_probe(struct platform_device *pdev)
+{
+ void __iomem *base = tegra_flowctrl_base;
+ struct resource *res;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ tegra_flowctrl_base = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(tegra_flowctrl_base))
+ return PTR_ERR(base);
+
+ iounmap(base);
+
+ return 0;
+}
+
+static const struct of_device_id tegra_flowctrl_match[] = {
+ { .compatible = "nvidia,tegra210-flowctrl" },
+ { .compatible = "nvidia,tegra124-flowctrl" },
+ { .compatible = "nvidia,tegra114-flowctrl" },
+ { .compatible = "nvidia,tegra30-flowctrl" },
+ { .compatible = "nvidia,tegra20-flowctrl" },
+ { }
+};
+
+static struct platform_driver tegra_flowctrl_driver = {
+ .driver = {
+ .name = "tegra-flowctrl",
+ .suppress_bind_attrs = true,
+ .of_match_table = tegra_flowctrl_match,
+ },
+ .probe = tegra_flowctrl_probe,
+};
+builtin_platform_driver(tegra_flowctrl_driver);
+
+static int __init tegra_flowctrl_init(void)
+{
+ struct resource res;
+ struct device_node *np;
+
+ if (!soc_is_tegra())
+ return 0;
+
+ np = of_find_matching_node(NULL, tegra_flowctrl_match);
+ if (np) {
+ if (of_address_to_resource(np, 0, &res) < 0) {
+ pr_err("failed to get flowctrl register\n");
+ return -ENXIO;
+ }
+ of_node_put(np);
+ } else if (IS_ENABLED(CONFIG_ARM)) {
+ /*
+ * Hardcoded fallback for 32-bit Tegra
+ * devices if device tree node is missing.
+ */
+ res.start = 0x60007000;
+ res.end = 0x60007fff;
+ res.flags = IORESOURCE_MEM;
+ } else {
+ /*
+ * At this point we're running on a Tegra,
+ * that doesn't support the flow controller
+ * (eg. Tegra186), so just return.
+ */
+ return 0;
+ }
+
+ tegra_flowctrl_base = ioremap_nocache(res.start, resource_size(&res));
+ if (!tegra_flowctrl_base)
+ return -ENXIO;
+
+ return 0;
+}
+early_initcall(tegra_flowctrl_init);
diff --git a/drivers/soc/tegra/fuse/fuse-tegra.c b/drivers/soc/tegra/fuse/fuse-tegra.c
index de2c1bfe28b5..7413f60fa855 100644
--- a/drivers/soc/tegra/fuse/fuse-tegra.c
+++ b/drivers/soc/tegra/fuse/fuse-tegra.c
@@ -18,7 +18,7 @@
#include <linux/clk.h>
#include <linux/device.h>
#include <linux/kobject.h>
-#include <linux/module.h>
+#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_address.h>
@@ -168,7 +168,7 @@ static struct platform_driver tegra_fuse_driver = {
},
.probe = tegra_fuse_probe,
};
-module_platform_driver(tegra_fuse_driver);
+builtin_platform_driver(tegra_fuse_driver);
bool __init tegra_fuse_read_spare(unsigned int spare)
{
diff --git a/drivers/soc/tegra/pmc-tegra186.c b/drivers/soc/tegra/pmc-tegra186.c
new file mode 100644
index 000000000000..6f5c6f98ba92
--- /dev/null
+++ b/drivers/soc/tegra/pmc-tegra186.c
@@ -0,0 +1,169 @@
+/*
+ * Copyright (c) 2016, NVIDIA CORPORATION. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ */
+
+#define pr_fmt(fmt) "tegra-pmc: " fmt
+
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/reboot.h>
+
+#include <asm/system_misc.h>
+
+#define PMC_CNTRL 0x000
+#define PMC_CNTRL_MAIN_RST BIT(4)
+
+#define PMC_RST_STATUS 0x070
+
+#define WAKE_AOWAKE_CTRL 0x4f4
+#define WAKE_AOWAKE_CTRL_INTR_POLARITY BIT(0)
+
+#define SCRATCH_SCRATCH0 0x2000
+#define SCRATCH_SCRATCH0_MODE_RECOVERY BIT(31)
+#define SCRATCH_SCRATCH0_MODE_BOOTLOADER BIT(30)
+#define SCRATCH_SCRATCH0_MODE_RCM BIT(1)
+#define SCRATCH_SCRATCH0_MODE_MASK (SCRATCH_SCRATCH0_MODE_RECOVERY | \
+ SCRATCH_SCRATCH0_MODE_BOOTLOADER | \
+ SCRATCH_SCRATCH0_MODE_RCM)
+
+struct tegra_pmc {
+ struct device *dev;
+ void __iomem *regs;
+ void __iomem *wake;
+ void __iomem *aotag;
+ void __iomem *scratch;
+
+ void (*system_restart)(enum reboot_mode mode, const char *cmd);
+ struct notifier_block restart;
+};
+
+static int tegra186_pmc_restart_notify(struct notifier_block *nb,
+ unsigned long action,
+ void *data)
+{
+ struct tegra_pmc *pmc = container_of(nb, struct tegra_pmc, restart);
+ const char *cmd = data;
+ u32 value;
+
+ value = readl(pmc->scratch + SCRATCH_SCRATCH0);
+ value &= ~SCRATCH_SCRATCH0_MODE_MASK;
+
+ if (cmd) {
+ if (strcmp(cmd, "recovery") == 0)
+ value |= SCRATCH_SCRATCH0_MODE_RECOVERY;
+
+ if (strcmp(cmd, "bootloader") == 0)
+ value |= SCRATCH_SCRATCH0_MODE_BOOTLOADER;
+
+ if (strcmp(cmd, "forced-recovery") == 0)
+ value |= SCRATCH_SCRATCH0_MODE_RCM;
+ }
+
+ writel(value, pmc->scratch + SCRATCH_SCRATCH0);
+
+ /*
+ * If available, call the system restart implementation that was
+ * registered earlier (typically PSCI).
+ */
+ if (pmc->system_restart) {
+ pmc->system_restart(reboot_mode, cmd);
+ return NOTIFY_DONE;
+ }
+
+ /* reset everything but SCRATCH0_SCRATCH0 and PMC_RST_STATUS */
+ value = readl(pmc->regs + PMC_CNTRL);
+ value |= PMC_CNTRL_MAIN_RST;
+ writel(value, pmc->regs + PMC_CNTRL);
+
+ return NOTIFY_DONE;
+}
+
+static int tegra186_pmc_setup(struct tegra_pmc *pmc)
+{
+ struct device_node *np = pmc->dev->of_node;
+ bool invert;
+ u32 value;
+
+ invert = of_property_read_bool(np, "nvidia,invert-interrupt");
+
+ value = readl(pmc->wake + WAKE_AOWAKE_CTRL);
+
+ if (invert)
+ value |= WAKE_AOWAKE_CTRL_INTR_POLARITY;
+ else
+ value &= ~WAKE_AOWAKE_CTRL_INTR_POLARITY;
+
+ writel(value, pmc->wake + WAKE_AOWAKE_CTRL);
+
+ /*
+ * We need to hook any system restart implementation registered
+ * previously so we can write SCRATCH_SCRATCH0 before reset.
+ */
+ pmc->system_restart = arm_pm_restart;
+ arm_pm_restart = NULL;
+
+ pmc->restart.notifier_call = tegra186_pmc_restart_notify;
+ pmc->restart.priority = 128;
+
+ return register_restart_handler(&pmc->restart);
+}
+
+static int tegra186_pmc_probe(struct platform_device *pdev)
+{
+ struct tegra_pmc *pmc;
+ struct resource *res;
+
+ pmc = devm_kzalloc(&pdev->dev, sizeof(*pmc), GFP_KERNEL);
+ if (!pmc)
+ return -ENOMEM;
+
+ pmc->dev = &pdev->dev;
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "pmc");
+ pmc->regs = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(pmc->regs))
+ return PTR_ERR(pmc->regs);
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "wake");
+ pmc->wake = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(pmc->wake))
+ return PTR_ERR(pmc->wake);
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "aotag");
+ pmc->aotag = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(pmc->aotag))
+ return PTR_ERR(pmc->aotag);
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "scratch");
+ pmc->scratch = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(pmc->scratch))
+ return PTR_ERR(pmc->scratch);
+
+ return tegra186_pmc_setup(pmc);
+}
+
+static const struct of_device_id tegra186_pmc_of_match[] = {
+ { .compatible = "nvidia,tegra186-pmc" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, tegra186_pmc_of_match);
+
+static struct platform_driver tegra186_pmc_driver = {
+ .driver = {
+ .name = "tegra186-pmc",
+ .of_match_table = tegra186_pmc_of_match,
+ },
+ .probe = tegra186_pmc_probe,
+};
+builtin_platform_driver(tegra186_pmc_driver);
diff --git a/drivers/soc/ti/Kconfig b/drivers/soc/ti/Kconfig
index 3557c5e32a93..39e152abe6b9 100644
--- a/drivers/soc/ti/Kconfig
+++ b/drivers/soc/ti/Kconfig
@@ -38,4 +38,16 @@ config WKUP_M3_IPC
to communicate and use the Wakeup M3 for PM features like suspend
resume and boots it using wkup_m3_rproc driver.
+config TI_SCI_PM_DOMAINS
+ tristate "TI SCI PM Domains Driver"
+ depends on TI_SCI_PROTOCOL
+ depends on PM_GENERIC_DOMAINS
+ help
+ Generic power domain implementation for TI device implementing
+ the TI SCI protocol.
+
+ To compile this as a module, choose M here. The module will be
+ called ti_sci_pm_domains. Note this is needed early in boot before
+ rootfs may be available.
+
endif # SOC_TI
diff --git a/drivers/soc/ti/Makefile b/drivers/soc/ti/Makefile
index 48ff3a79634f..7d572736c86e 100644
--- a/drivers/soc/ti/Makefile
+++ b/drivers/soc/ti/Makefile
@@ -5,3 +5,4 @@ obj-$(CONFIG_KEYSTONE_NAVIGATOR_QMSS) += knav_qmss.o
knav_qmss-y := knav_qmss_queue.o knav_qmss_acc.o
obj-$(CONFIG_KEYSTONE_NAVIGATOR_DMA) += knav_dma.o
obj-$(CONFIG_WKUP_M3_IPC) += wkup_m3_ipc.o
+obj-$(CONFIG_TI_SCI_PM_DOMAINS) += ti_sci_pm_domains.o
diff --git a/drivers/soc/ti/ti_sci_pm_domains.c b/drivers/soc/ti/ti_sci_pm_domains.c
new file mode 100644
index 000000000000..b0b283810e72
--- /dev/null
+++ b/drivers/soc/ti/ti_sci_pm_domains.c
@@ -0,0 +1,202 @@
+/*
+ * TI SCI Generic Power Domain Driver
+ *
+ * Copyright (C) 2015-2017 Texas Instruments Incorporated - http://www.ti.com/
+ * J Keerthy <j-keerthy@ti.com>
+ * Dave Gerlach <d-gerlach@ti.com>
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pm_domain.h>
+#include <linux/slab.h>
+#include <linux/soc/ti/ti_sci_protocol.h>
+
+/**
+ * struct ti_sci_genpd_dev_data: holds data needed for every device attached
+ * to this genpd
+ * @idx: index of the device that identifies it with the system
+ * control processor.
+ */
+struct ti_sci_genpd_dev_data {
+ int idx;
+};
+
+/**
+ * struct ti_sci_pm_domain: TI specific data needed for power domain
+ * @ti_sci: handle to TI SCI protocol driver that provides ops to
+ * communicate with system control processor.
+ * @dev: pointer to dev for the driver for devm allocs
+ * @pd: generic_pm_domain for use with the genpd framework
+ */
+struct ti_sci_pm_domain {
+ const struct ti_sci_handle *ti_sci;
+ struct device *dev;
+ struct generic_pm_domain pd;
+};
+
+#define genpd_to_ti_sci_pd(gpd) container_of(gpd, struct ti_sci_pm_domain, pd)
+
+/**
+ * ti_sci_dev_id(): get prepopulated ti_sci id from struct dev
+ * @dev: pointer to device associated with this genpd
+ *
+ * Returns device_id stored from ti,sci_id property
+ */
+static int ti_sci_dev_id(struct device *dev)
+{
+ struct generic_pm_domain_data *genpd_data = dev_gpd_data(dev);
+ struct ti_sci_genpd_dev_data *sci_dev_data = genpd_data->data;
+
+ return sci_dev_data->idx;
+}
+
+/**
+ * ti_sci_dev_to_sci_handle(): get pointer to ti_sci_handle
+ * @dev: pointer to device associated with this genpd
+ *
+ * Returns ti_sci_handle to be used to communicate with system
+ * control processor.
+ */
+static const struct ti_sci_handle *ti_sci_dev_to_sci_handle(struct device *dev)
+{
+ struct generic_pm_domain *pd = pd_to_genpd(dev->pm_domain);
+ struct ti_sci_pm_domain *ti_sci_genpd = genpd_to_ti_sci_pd(pd);
+
+ return ti_sci_genpd->ti_sci;
+}
+
+/**
+ * ti_sci_dev_start(): genpd device start hook called to turn device on
+ * @dev: pointer to device associated with this genpd to be powered on
+ */
+static int ti_sci_dev_start(struct device *dev)
+{
+ const struct ti_sci_handle *ti_sci = ti_sci_dev_to_sci_handle(dev);
+ int idx = ti_sci_dev_id(dev);
+
+ return ti_sci->ops.dev_ops.get_device(ti_sci, idx);
+}
+
+/**
+ * ti_sci_dev_stop(): genpd device stop hook called to turn device off
+ * @dev: pointer to device associated with this genpd to be powered off
+ */
+static int ti_sci_dev_stop(struct device *dev)
+{
+ const struct ti_sci_handle *ti_sci = ti_sci_dev_to_sci_handle(dev);
+ int idx = ti_sci_dev_id(dev);
+
+ return ti_sci->ops.dev_ops.put_device(ti_sci, idx);
+}
+
+static int ti_sci_pd_attach_dev(struct generic_pm_domain *domain,
+ struct device *dev)
+{
+ struct device_node *np = dev->of_node;
+ struct of_phandle_args pd_args;
+ struct ti_sci_pm_domain *ti_sci_genpd = genpd_to_ti_sci_pd(domain);
+ const struct ti_sci_handle *ti_sci = ti_sci_genpd->ti_sci;
+ struct ti_sci_genpd_dev_data *sci_dev_data;
+ struct generic_pm_domain_data *genpd_data;
+ int idx, ret = 0;
+
+ ret = of_parse_phandle_with_args(np, "power-domains",
+ "#power-domain-cells", 0, &pd_args);
+ if (ret < 0)
+ return ret;
+
+ if (pd_args.args_count != 1)
+ return -EINVAL;
+
+ idx = pd_args.args[0];
+
+ /*
+ * Check the validity of the requested idx, if the index is not valid
+ * the PMMC will return a NAK here and we will not allocate it.
+ */
+ ret = ti_sci->ops.dev_ops.is_valid(ti_sci, idx);
+ if (ret)
+ return -EINVAL;
+
+ sci_dev_data = kzalloc(sizeof(*sci_dev_data), GFP_KERNEL);
+ if (!sci_dev_data)
+ return -ENOMEM;
+
+ sci_dev_data->idx = idx;
+
+ genpd_data = dev_gpd_data(dev);
+ genpd_data->data = sci_dev_data;
+
+ return 0;
+}
+
+static void ti_sci_pd_detach_dev(struct generic_pm_domain *domain,
+ struct device *dev)
+{
+ struct generic_pm_domain_data *genpd_data = dev_gpd_data(dev);
+ struct ti_sci_genpd_dev_data *sci_dev_data = genpd_data->data;
+
+ kfree(sci_dev_data);
+ genpd_data->data = NULL;
+}
+
+static const struct of_device_id ti_sci_pm_domain_matches[] = {
+ { .compatible = "ti,sci-pm-domain", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, ti_sci_pm_domain_matches);
+
+static int ti_sci_pm_domain_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ struct ti_sci_pm_domain *ti_sci_pd;
+ int ret;
+
+ ti_sci_pd = devm_kzalloc(dev, sizeof(*ti_sci_pd), GFP_KERNEL);
+ if (!ti_sci_pd)
+ return -ENOMEM;
+
+ ti_sci_pd->ti_sci = devm_ti_sci_get_handle(dev);
+ if (IS_ERR(ti_sci_pd->ti_sci))
+ return PTR_ERR(ti_sci_pd->ti_sci);
+
+ ti_sci_pd->dev = dev;
+
+ ti_sci_pd->pd.attach_dev = ti_sci_pd_attach_dev;
+ ti_sci_pd->pd.detach_dev = ti_sci_pd_detach_dev;
+
+ ti_sci_pd->pd.dev_ops.start = ti_sci_dev_start;
+ ti_sci_pd->pd.dev_ops.stop = ti_sci_dev_stop;
+
+ pm_genpd_init(&ti_sci_pd->pd, NULL, true);
+
+ ret = of_genpd_add_provider_simple(np, &ti_sci_pd->pd);
+
+ return ret;
+}
+
+static struct platform_driver ti_sci_pm_domains_driver = {
+ .probe = ti_sci_pm_domain_probe,
+ .driver = {
+ .name = "ti_sci_pm_domains",
+ .of_match_table = ti_sci_pm_domain_matches,
+ },
+};
+module_platform_driver(ti_sci_pm_domains_driver);
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("TI System Control Interface (SCI) Power Domain driver");
+MODULE_AUTHOR("Dave Gerlach");
diff --git a/drivers/soc/zte/zx296718_pm_domains.c b/drivers/soc/zte/zx296718_pm_domains.c
index 5ed924fee855..4dc5d62ee81b 100644
--- a/drivers/soc/zte/zx296718_pm_domains.c
+++ b/drivers/soc/zte/zx296718_pm_domains.c
@@ -169,7 +169,6 @@ static const struct of_device_id zx296718_pm_domain_matches[] = {
static struct platform_driver zx296718_pd_driver = {
.driver = {
.name = "zx296718-powerdomain",
- .owner = THIS_MODULE,
.of_match_table = zx296718_pm_domain_matches,
},
.probe = zx296718_pd_probe,
diff --git a/drivers/soc/zte/zx2967_pm_domains.c b/drivers/soc/zte/zx2967_pm_domains.c
index 61c8d84bf315..c42aeaaa34ba 100644
--- a/drivers/soc/zte/zx2967_pm_domains.c
+++ b/drivers/soc/zte/zx2967_pm_domains.c
@@ -125,10 +125,8 @@ int zx2967_pd_probe(struct platform_device *pdev,
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
pcubase = devm_ioremap_resource(&pdev->dev, res);
- if (IS_ERR(pcubase)) {
- dev_err(&pdev->dev, "ioremap fail.\n");
+ if (IS_ERR(pcubase))
return PTR_ERR(pcubase);
- }
for (i = 0; i < domain_num; ++i) {
zx_pm_domains[i]->power_on = zx2967_power_on;