--- /dev/null
+/*
+ * SAMSUNG EXYNOS9810 SoC PM Domains device tree source
+ *
+ * Copyright (c) 2017 Samsung Electronics Co., Ltd.
+ * http://www.samsung.com
+ *
+ * SAMSUNG EXYNOS9810 SoC PM domains device nodes are listed in this file.
+ * EXYNOS9810 based board files can include this file and provide
+ * values for board specfic bindings.
+ *
+ * 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.
+ */
+#include <dt-bindings/power/exynos-power.h>
+
+/ {
+ pd_cam: pd-cam@11864020 {
+ compatible = "samsung,exynos-pd";
+ reg = <0x0 0x11864020 0x20>;
+ cal_id = <0xB1380000>;
+ need_smc = <0x14510204>;
+ status = "okay";
+ };
+
+ pd_isp: pd-isp@11864080 {
+ compatible = "samsung,exynos-pd";
+ reg = <0x0 0x11864080 0x20>;
+ cal_id = <0xB1380001>;
+ need_smc = <0x14710204>;
+ status = "okay";
+ parent = <&pd_cam>;
+ };
+
+ pd_vipx1: pd-vipx1@118640e0 {
+ compatible = "samsung,exynos-pd";
+ reg = <0x0 0x118640e0 0x20>;
+ cal_id = <0xB1380002>;
+ need_smc = <0x10C10204>;
+ status = "okay";
+ };
+
+ pd_vipx2: pd-vipx2@118640e0 {
+ compatible = "samsung,exynos-pd";
+ reg = <0x0 0x118640e0 0x20>;
+ cal_id = <0xB1380003>;
+ need_smc = <0x10e10204>;
+ status = "okay";
+ parent = <&pd_vipx1>;
+ };
+
+ pd_g2d: pd-g2d@11864040 {
+ compatible = "samsung,exynos-pd";
+ reg = <0x0 0x11864040 0x20>;
+ cal_id = <0xB1380004>;
+ need_smc = <0x12e10204>;
+ status = "okay";
+ };
+
+ pd_g3d: pd-g3d@11864060 {
+ compatible = "samsung,exynos-pd";
+ reg = <0x0 0x11864060 0x20>;
+ cal_id = <0xB1380005>;
+ need_smc = <0x11410204>;
+ status = "okay";
+ };
+
+ pd_dispaud: pd-dispaud@11864000 {
+ compatible = "samsung,exynos-pd";
+ reg = <0x0 0x11864000 0x20>;
+ cal_id = <0xB1380006>;
+ need_smc = <0x14810204>;
+ status = "okay";
+ power-down-ok = <PD_OK_AUD>;
+ };
+
+ pd_mfc: pd-mfc@118640a0 {
+ compatible = "samsung,exynos-pd";
+ reg = <0x0 0x118640a0 0x20>;
+ cal_id = <0xB1380007>;
+ need_smc = <0x12C10204>;
+ status = "okay";
+ };
+
+ dbgdev-pd-cam {
+ compatible = "samsung,exynos-pd-dbg";
+ samsung,power-domain = <&pd_cam>;
+ };
+
+ dbgdev-pd-isp {
+ compatible = "samsung,exynos-pd-dbg";
+ samsung,power-domain = <&pd_isp>;
+ };
+
+ dbgdev-pd-vipx1 {
+ compatible = "samsung,exynos-pd-dbg";
+ samsung,power-domain = <&pd_vipx1>;
+ };
+
+ dbgdev-pd-vipx2 {
+ compatible = "samsung,exynos-pd-dbg";
+ samsung,power-domain = <&pd_vipx2>;
+ };
+
+ dbgdev-pd-g2d {
+ compatible = "samsung,exynos-pd-dbg";
+ samsung,power-domain = <&pd_g2d>;
+ };
+
+ dbgdev-pd-g3d {
+ compatible = "samsung,exynos-pd-dbg";
+ samsung,power-domain = <&pd_g3d>;
+ };
+
+ dbgdev-pd-dispaud {
+ compatible = "samsung,exynos-pd-dbg";
+ samsung,power-domain = <&pd_dispaud>;
+ };
+
+ dbgdev-pd-mfc {
+ compatible = "samsung,exynos-pd-dbg";
+ samsung,power-domain = <&pd_mfc>;
+ };
+
+};
#include <dt-bindings/ufs/ufs.h>
#include "exynos9610-sysmmu.dtsi"
#include <dt-bindings/soc/samsung/exynos9610-dm.h>
+#include "exynos9610-pm-domains.dtsi"
/ {
compatible = "samsung,armv8", "samsung,exynos9610";
Enable DVFS Manager for Exynos SoC.
This module controls constraint between each DVFS domains.
+config EXYNOS_PD
+ bool "Exynos PM domain Support"
+ depends on ARCH_EXYNOS
+ depends on PM
+ select PM_GENERIC_DOMAINS
+
endif
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
obj-$(CONFIG_EXYNOS_CHIPID) += exynos-chipid.o
obj-$(CONFIG_EXYNOS_DEBUG) += debug/
# DVFS
obj-$(CONFIG_EXYNOS_DVFS_MANAGER) += exynos-dm.o
+
+# PD
+obj-$(CONFIG_EXYNOS_PD) += exynos-pd.o exynos-pd-dbg.o
+obj-$(CONFIG_EXYNOS_PM_DOMAINS) += pm_domains.o
--- /dev/null
+/*
+ * Exynos PM domain debugfs support.
+ *
+ * Copyright (c) 2016 Samsung Electronics Co., Ltd.
+ * http://www.samsung.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.
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of_address.h>
+#include <linux/of_platform.h>
+#include <linux/sched.h>
+#include <linux/fs.h>
+#include <linux/uaccess.h>
+
+#include <soc/samsung/exynos-pd.h>
+
+#ifdef CONFIG_DEBUG_FS
+static struct dentry *exynos_pd_dbg_root;
+
+static int exynos_pd_dbg_long_test(struct device *dev)
+{
+ int ret, i;
+
+ pr_info("%s %s: test start.\n", EXYNOS_PD_DBG_PREFIX, __func__);
+
+ if (pm_runtime_enabled(dev) && pm_runtime_active(dev)) {
+ ret = pm_runtime_put_sync(dev);
+ if (ret) {
+ pr_err("%s %s: put sync failed.\n",
+ EXYNOS_PD_DBG_PREFIX, __func__);
+ return ret;
+ }
+ }
+
+ for (i = 0; i < 100; i++) {
+ ret = pm_runtime_get_sync(dev);
+ if (ret) {
+ pr_err("%s %s: get sync failed.\n",
+ EXYNOS_PD_DBG_PREFIX, __func__);
+ return ret;
+ }
+ mdelay(50);
+ ret = pm_runtime_put_sync(dev);
+ if (ret) {
+ pr_err("%s %s: put sync failed.\n",
+ EXYNOS_PD_DBG_PREFIX, __func__);
+ return ret;
+ }
+ mdelay(50);
+ }
+
+ pr_info("%s %s: test done.\n", EXYNOS_PD_DBG_PREFIX, __func__);
+
+ return ret;
+}
+
+static struct generic_pm_domain *exynos_pd_dbg_dev_to_genpd(struct device *dev)
+{
+ if (IS_ERR_OR_NULL(dev->pm_domain))
+ return ERR_PTR(-EINVAL);
+
+ return pd_to_genpd(dev->pm_domain);
+}
+
+static void exynos_pd_dbg_summary_show(struct generic_pm_domain *genpd)
+{
+ static const char * const gpd_status_lookup[] = {
+ [GPD_STATE_ACTIVE] = "on",
+ [GPD_STATE_POWER_OFF] = "off"
+ };
+ static const char * const rpm_status_lookup[] = {
+ [RPM_ACTIVE] = "active",
+ [RPM_RESUMING] = "resuming",
+ [RPM_SUSPENDED] = "suspended",
+ [RPM_SUSPENDING] = "suspending"
+ };
+ const char *p = "";
+ struct pm_domain_data *pm_data;
+ struct gpd_link *link;
+
+ mutex_lock(&genpd->lock);
+
+ if (genpd->status >= ARRAY_SIZE(gpd_status_lookup)) {
+ pr_err("%s invalid GPD_STATUS\n", EXYNOS_PD_DBG_PREFIX);
+ mutex_unlock(&genpd->lock);
+ return ;
+ }
+
+ pr_info("[GENPD] : %-30s [GPD_STATUS] : %-15s\n",
+ genpd->name, gpd_status_lookup[genpd->status]);
+
+ list_for_each_entry(pm_data, &genpd->dev_list, list_node) {
+ if (pm_data->dev->power.runtime_error)
+ p = "error";
+ else if (pm_data->dev->power.disable_depth)
+ p = "unsupported";
+ else if (pm_data->dev->power.runtime_status < ARRAY_SIZE(rpm_status_lookup))
+ p = rpm_status_lookup[pm_data->dev->power.runtime_status];
+ else
+ WARN_ON(1);
+
+ pr_info("\t[DEV] : %-30s [RPM_STATUS] : %-15s\n",
+ dev_name(pm_data->dev), p);
+ }
+
+ list_for_each_entry(link, &genpd->master_links, master_node)
+ exynos_pd_dbg_summary_show(link->slave);
+
+ mutex_unlock(&genpd->lock);
+}
+
+static ssize_t exynos_pd_dbg_read(struct file *file, char __user *user_buf,
+ size_t count, loff_t *ppos)
+{
+ struct device *dev = file->private_data;
+ struct generic_pm_domain *genpd = exynos_pd_dbg_dev_to_genpd(dev);
+
+ exynos_pd_dbg_summary_show(genpd);
+
+ return 0;
+}
+
+static ssize_t exynos_pd_dbg_write(struct file *file, const char __user *user_buf,
+ size_t count, loff_t *ppos)
+{
+ struct device *dev = file->private_data;
+ char buf[32];
+ size_t buf_size;
+
+ buf_size = min(count, (sizeof(buf)-1));
+ if (copy_from_user(buf, user_buf, buf_size))
+ return -EFAULT;
+
+ switch (buf[0]) {
+ case '0':
+ if (pm_runtime_put_sync(dev))
+ pr_err("%s %s: put sync failed.\n",
+ EXYNOS_PD_DBG_PREFIX, __func__);
+ break;
+ case '1':
+ if (pm_runtime_get_sync(dev))
+ pr_err("%s %s: get sync failed.\n",
+ EXYNOS_PD_DBG_PREFIX, __func__);
+ break;
+ case 'c':
+ exynos_pd_dbg_long_test(dev);
+ break;
+ default:
+ pr_err("%s %s: Invalid input ['0'|'1'|'c']\n",
+ EXYNOS_PD_DBG_PREFIX, __func__);
+ break;
+ }
+
+ return count;
+}
+
+static const struct file_operations exynos_pd_dbg_fops = {
+ .open = simple_open,
+ .read = exynos_pd_dbg_read,
+ .write = exynos_pd_dbg_write,
+ .llseek = default_llseek,
+};
+#endif
+
+static int exynos_pd_dbg_probe(struct platform_device *pdev)
+{
+ int ret;
+ struct exynos_pd_dbg_info *dbg_info;
+
+ dbg_info = kzalloc(sizeof(struct exynos_pd_dbg_info), GFP_KERNEL);
+ if (!dbg_info) {
+ pr_err("%s %s: could not allocate mem for dbg_info\n",
+ EXYNOS_PD_DBG_PREFIX, __func__);
+ ret = -ENOMEM;
+ goto err_dbg_info;
+ }
+ dbg_info->dev = &pdev->dev;
+#ifdef CONFIG_DEBUG_FS
+ if (!exynos_pd_dbg_root) {
+ exynos_pd_dbg_root = debugfs_create_dir("exynos-pd", NULL);
+ if (!exynos_pd_dbg_root) {
+ pr_err("%s %s: could not create debugfs dir\n",
+ EXYNOS_PD_DBG_PREFIX, __func__);
+ ret = -ENOMEM;
+ goto err_dbgfs_root;
+ }
+ }
+
+ dbg_info->fops = exynos_pd_dbg_fops;
+ dbg_info->d = debugfs_create_file(dev_name(&pdev->dev), 0644,
+ exynos_pd_dbg_root, dbg_info->dev, &dbg_info->fops);
+ if (!dbg_info->d) {
+ pr_err("%s %s: could not creatd debugfs file\n",
+ EXYNOS_PD_DBG_PREFIX, __func__);
+ ret = -ENOMEM;
+ goto err_dbgfs_pd;
+ }
+#endif
+ platform_set_drvdata(pdev, dbg_info);
+
+ pm_runtime_enable(&pdev->dev);
+
+ ret = pm_runtime_get_sync(&pdev->dev);
+ if (ret) {
+ pr_err("%s %s: get_sync of %s failed.\n",
+ EXYNOS_PD_DBG_PREFIX, __func__, dev_name(&pdev->dev));
+ goto err_get_sync;
+ }
+
+ ret = pm_runtime_put_sync(&pdev->dev);
+ if (ret) {
+ pr_err("%s %s: put sync of %s failed.\n",
+ EXYNOS_PD_DBG_PREFIX, __func__, dev_name(&pdev->dev));
+ goto err_put_sync;
+ }
+
+ return 0;
+
+err_get_sync:
+err_put_sync:
+#ifdef CONFIG_DEBUG_FS
+ debugfs_remove_recursive(dbg_info->d);
+err_dbgfs_pd:
+ debugfs_remove_recursive(exynos_pd_dbg_root);
+err_dbgfs_root:
+#endif
+ kfree(dbg_info);
+err_dbg_info:
+ return ret;
+}
+
+static int exynos_pd_dbg_remove(struct platform_device *pdev)
+{
+ struct exynos_pd_dbg_info *dbg_info = platform_get_drvdata(pdev);
+ struct device *dev = dbg_info->dev;
+
+ if (pm_runtime_enabled(dev) && pm_runtime_active(dev))
+ pm_runtime_put_sync(dev);
+
+ pm_runtime_disable(dev);
+
+#ifdef CONFIG_DEBUG_FS
+ debugfs_remove_recursive(dbg_info->d);
+ debugfs_remove_recursive(exynos_pd_dbg_root);
+#endif
+ kfree(dbg_info);
+
+ platform_set_drvdata(pdev, NULL);
+
+ return 0;
+}
+
+static int exynos_pd_dbg_runtime_suspend(struct device *dev)
+{
+ pr_info("%s %s's Runtime_Suspend\n",
+ EXYNOS_PD_DBG_PREFIX, dev_name(dev));
+ return 0;
+}
+
+static int exynos_pd_dbg_runtime_resume(struct device *dev)
+{
+ pr_info("%s %s's Runtime_Resume\n",
+ EXYNOS_PD_DBG_PREFIX, dev_name(dev));
+ return 0;
+}
+
+static struct dev_pm_ops exynos_pd_dbg_pm_ops = {
+ SET_RUNTIME_PM_OPS(exynos_pd_dbg_runtime_suspend,
+ exynos_pd_dbg_runtime_resume,
+ NULL)
+};
+
+#ifdef CONFIG_OF
+static const struct of_device_id exynos_pd_dbg_match[] = {
+ {
+ .compatible = "samsung,exynos-pd-dbg",
+ },
+ {},
+};
+#endif
+
+static struct platform_driver exynos_pd_dbg_drv = {
+ .probe = exynos_pd_dbg_probe,
+ .remove = exynos_pd_dbg_remove,
+ .driver = {
+ .name = "exynos_pd_dbg",
+ .owner = THIS_MODULE,
+ .pm = &exynos_pd_dbg_pm_ops,
+#ifdef CONFIG_OF
+ .of_match_table = exynos_pd_dbg_match,
+#endif
+ },
+};
+
+static int __init exynos_pd_dbg_init(void)
+{
+ return platform_driver_register(&exynos_pd_dbg_drv);
+}
+late_initcall(exynos_pd_dbg_init);
--- /dev/null
+/*
+ * Exynos PM domain support for PMUCAL 3.0 interface.
+ *
+ * Copyright (c) 2016 Samsung Electronics Co., Ltd.
+ * http://www.samsung.com
+ *
+ * Implementation of Exynos specific power domain control which is used in
+ * conjunction with runtime-pm.
+ *
+ * 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.
+ */
+
+#include <soc/samsung/exynos-pd.h>
+#include <soc/samsung/bts.h>
+#include <soc/samsung/cal-if.h>
+#include <linux/apm-exynos.h>
+struct exynos_pm_domain *exynos_pd_lookup_name(const char *domain_name)
+{
+ struct exynos_pm_domain *exypd = NULL;
+ struct device_node *np;
+
+ if (IS_ERR_OR_NULL(domain_name))
+ return NULL;
+
+ for_each_compatible_node(np, NULL, "samsung,exynos-pd") {
+ struct platform_device *pdev;
+ struct exynos_pm_domain *pd;
+
+ if (of_device_is_available(np)) {
+ pdev = of_find_device_by_node(np);
+ if (!pdev)
+ continue;
+ pd = platform_get_drvdata(pdev);
+ if (!strcmp(pd->name, domain_name)) {
+ exypd = pd;
+ break;
+ }
+ }
+ }
+ return exypd;
+}
+EXPORT_SYMBOL(exynos_pd_lookup_name);
+
+static int exynos_pd_status(struct exynos_pm_domain *pd)
+{
+ int status;
+
+ if (unlikely(!pd))
+ return -EINVAL;
+
+ mutex_lock(&pd->access_lock);
+ status = cal_pd_status(pd->cal_pdid);
+ mutex_unlock(&pd->access_lock);
+
+ return status;
+}
+
+/* Power domain on sequence.
+ * on_pre, on_post functions are registered as notification handler at CAL code.
+ */
+static void exynos_pd_power_on_pre(struct exynos_pm_domain *pd)
+{
+ exynos_update_ip_idle_status(pd->idle_ip_index, 0);
+
+ if (pd->devfreq_index >= 0) {
+ exynos_bts_scitoken_setting(true);
+ }
+}
+
+static void exynos_pd_power_on_post(struct exynos_pm_domain *pd)
+{
+#if defined(CONFIG_EXYNOS_BCM)
+ if(cal_pd_status(pd->cal_pdid) && pd->bcm)
+ bcm_pd_sync(pd->bcm, true);
+#endif
+}
+
+static void exynos_pd_power_off_pre(struct exynos_pm_domain *pd)
+{
+#ifdef CONFIG_EXYNOS_CL_DVFS_G3D
+ if (!strcmp(pd->name, "pd-g3d")) {
+ exynos_g3d_power_down_noti_apm();
+ }
+#endif /* CONFIG_EXYNOS_CL_DVFS_G3D */
+#if defined(CONFIG_EXYNOS_BCM)
+ if(cal_pd_status(pd->cal_pdid) && pd->bcm)
+ bcm_pd_sync(pd->bcm, false);
+#endif
+}
+
+static void exynos_pd_power_off_post(struct exynos_pm_domain *pd)
+{
+ exynos_update_ip_idle_status(pd->idle_ip_index, 1);
+
+ if (pd->devfreq_index >= 0) {
+ exynos_bts_scitoken_setting(false);
+ }
+}
+
+static void exynos_pd_prepare_forced_off(struct exynos_pm_domain *pd)
+{
+}
+
+static int exynos_pd_power_on(struct generic_pm_domain *genpd)
+{
+ struct exynos_pm_domain *pd = container_of(genpd, struct exynos_pm_domain, genpd);
+ int ret = 0;
+
+ mutex_lock(&pd->access_lock);
+
+ DEBUG_PRINT_INFO("%s(%s)+\n", __func__, pd->name);
+
+ if (unlikely(!pd->pd_control)) {
+ pr_debug(EXYNOS_PD_PREFIX "%s is Logical sub power domain, dose not have to power on control\n", pd->name);
+ goto acc_unlock;
+ }
+
+ if (pd->power_down_skipped) {
+ pr_info(EXYNOS_PD_PREFIX "%s power-on is skipped.\n", pd->name);
+ goto acc_unlock;
+ }
+
+ exynos_pd_power_on_pre(pd);
+
+ ret = pd->pd_control(pd->cal_pdid, 1);
+ if (ret) {
+ pr_err(EXYNOS_PD_PREFIX "%s cannot be powered on\n", pd->name);
+ exynos_pd_power_off_post(pd);
+ ret = -EAGAIN;
+ goto acc_unlock;
+ }
+
+ exynos_pd_power_on_post(pd);
+
+acc_unlock:
+ DEBUG_PRINT_INFO("%s(%s)-, ret = %d\n", __func__, pd->name, ret);
+ mutex_unlock(&pd->access_lock);
+
+ return ret;
+}
+
+static int exynos_pd_power_off(struct generic_pm_domain *genpd)
+{
+ struct exynos_pm_domain *pd = container_of(genpd, struct exynos_pm_domain, genpd);
+ int ret = 0;
+
+ mutex_lock(&pd->access_lock);
+
+ DEBUG_PRINT_INFO("%s(%s)+\n", __func__, pd->name);
+
+ if (unlikely(!pd->pd_control)) {
+ pr_debug(EXYNOS_PD_PREFIX "%s is Logical sub power domain, dose not have to power off control\n", genpd->name);
+ goto acc_unlock;
+ }
+
+ if (pd->power_down_ok && !pd->power_down_ok()) {
+ pr_info(EXYNOS_PD_PREFIX "%s power-off is skipped.\n", pd->name);
+ pd->power_down_skipped = true;
+ goto acc_unlock;
+ }
+
+ exynos_pd_power_off_pre(pd);
+
+ ret = pd->pd_control(pd->cal_pdid, 0);
+ if (unlikely(ret)) {
+ if (ret == -4) {
+ pr_err(EXYNOS_PD_PREFIX "Timed out during %s power off! -> forced power off\n", genpd->name);
+ exynos_pd_prepare_forced_off(pd);
+ ret = pd->pd_control(pd->cal_pdid, 0);
+ if (unlikely(ret)) {
+ pr_err(EXYNOS_PD_PREFIX "%s occur error at power off!\n", genpd->name);
+ goto acc_unlock;
+ }
+ } else {
+ pr_err(EXYNOS_PD_PREFIX "%s occur error at power off!\n", genpd->name);
+ goto acc_unlock;
+ }
+ }
+
+ exynos_pd_power_off_post(pd);
+ pd->power_down_skipped = false;
+
+acc_unlock:
+ DEBUG_PRINT_INFO("%s(%s)-, ret = %d\n", __func__, pd->name, ret);
+ mutex_unlock(&pd->access_lock);
+
+ return ret;
+}
+
+#ifdef CONFIG_OF
+/**
+ * of_get_devfreq_sync_volt_idx - check devfreq sync voltage idx
+ *
+ * Returns the index if the "devfreq-sync-voltage" is described in DT pd node,
+ * -ENOENT otherwise.
+ */
+static int of_get_devfreq_sync_volt_idx(const struct device_node *device)
+{
+ int ret;
+ u32 val;
+
+ ret = of_property_read_u32(device, "devfreq-sync-voltage", &val);
+ if (ret)
+ return -ENOENT;
+
+ return val;
+}
+
+static bool exynos_pd_power_down_ok_aud(void)
+{
+#ifdef CONFIG_SND_SOC_SAMSUNG_ABOX
+#ifdef CONFIG_SOC_EMULATOR8895
+ return false;
+#else
+ return !abox_is_on();
+#endif
+#else
+ return true;
+#endif
+}
+
+static bool exynos_pd_power_down_ok_vts(void)
+{
+#ifdef CONFIG_SND_SOC_SAMSUNG_VTS
+ return !vts_is_on();
+#else
+ return true;
+#endif
+}
+
+static void of_get_power_down_ok(struct exynos_pm_domain *pd)
+{
+ int ret;
+ u32 val;
+ struct device_node *device = pd->of_node;
+
+ ret = of_property_read_u32(device, "power-down-ok", &val);
+ if (ret)
+ return ;
+ else {
+ switch (val) {
+ case PD_OK_AUD:
+ pd->power_down_ok = exynos_pd_power_down_ok_aud;
+ break;
+ case PD_OK_VTS:
+ pd->power_down_ok = exynos_pd_power_down_ok_vts;
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+static void exynos_pd_genpd_init(struct exynos_pm_domain *pd, int state)
+{
+ pd->genpd.name = pd->name;
+ pd->genpd.power_off = exynos_pd_power_off;
+ pd->genpd.power_on = exynos_pd_power_on;
+
+ /* pd power on/off latency is less than 1ms */
+ pd->genpd.states[0].power_on_latency_ns = 1000000;
+ pd->genpd.states[0].power_off_latency_ns = 1000000;
+
+ pm_genpd_init(&pd->genpd, NULL, state ? false : true);
+}
+
+/* exynos_pd_show_power_domain - show current power domain status.
+ *
+ * read the status of power domain and show it.
+ */
+static void exynos_pd_show_power_domain(void)
+{
+ struct device_node *np;
+ for_each_compatible_node(np, NULL, "samsung,exynos-pd") {
+ struct platform_device *pdev;
+ struct exynos_pm_domain *pd;
+
+ if (of_device_is_available(np)) {
+ pdev = of_find_device_by_node(np);
+ if (!pdev)
+ continue;
+ pd = platform_get_drvdata(pdev);
+ pr_info(" %-9s - %-3s\n", pd->genpd.name,
+ cal_pd_status(pd->cal_pdid) ? "on" : "off");
+ } else
+ pr_info(" %-9s - %s\n", np->name, "on, always");
+ }
+
+ return;
+}
+
+static __init int exynos_pd_dt_parse(void)
+{
+ struct platform_device *pdev = NULL;
+ struct device_node *np = NULL;
+ int ret = 0;
+
+ for_each_compatible_node(np, NULL, "samsung,exynos-pd") {
+ struct exynos_pm_domain *pd;
+ struct device_node *children;
+ int initial_state;
+
+ /* skip unmanaged power domain */
+ if (!of_device_is_available(np))
+ continue;
+
+ pdev = of_find_device_by_node(np);
+
+ pd = kzalloc(sizeof(*pd), GFP_KERNEL);
+ if (!pd) {
+ pr_err(EXYNOS_PD_PREFIX "%s: failed to allocate memory for domain\n",
+ __func__);
+ return -ENOMEM;
+ }
+
+ /* init exynos_pm_domain's members */
+ pd->name = kstrdup(np->name, GFP_KERNEL);
+ ret = of_property_read_u32(np, "cal_id", (u32 *)&pd->cal_pdid);
+ if (ret) {
+ pr_err(EXYNOS_PD_PREFIX "%s: failed to get cal_pdid from of %s\n",
+ __func__, pd->name);
+ return -ENODEV;
+ }
+ pd->of_node = np;
+ pd->pd_control = cal_pd_control;
+ pd->check_status = exynos_pd_status;
+ pd->devfreq_index = of_get_devfreq_sync_volt_idx(pd->of_node);
+ of_get_power_down_ok(pd);
+ pd->power_down_skipped = false;
+
+ ret = of_property_read_u32(np, "need_smc", (u32 *)&pd->need_smc);
+ if (ret) {
+ pd->need_smc = 0x0;
+ } else {
+ cal_pd_set_smc_id(pd->cal_pdid, pd->need_smc);
+ pr_info(EXYNOS_PD_PREFIX "%s: %s read need_smc 0x%x successfully.!\n",
+ __func__, pd->name, pd->need_smc);
+ }
+ initial_state = cal_pd_status(pd->cal_pdid);
+ if (initial_state == -1) {
+ pr_err(EXYNOS_PD_PREFIX "%s: %s is in unknown state\n",
+ __func__, pd->name);
+ return -EINVAL;
+ }
+
+ pd->idle_ip_index = exynos_get_idle_ip_index(pd->name);
+
+ mutex_init(&pd->access_lock);
+ platform_set_drvdata(pdev, pd);
+
+ exynos_pd_genpd_init(pd, initial_state);
+ of_genpd_add_provider_simple(np, &pd->genpd);
+
+ /* add LOGICAL sub-domain
+ * It is not assumed that there is REAL sub-domain.
+ * Power on/off functions are not defined here.
+ */
+ for_each_child_of_node(np, children) {
+ struct exynos_pm_domain *sub_pd;
+ struct platform_device *sub_pdev;
+
+ sub_pd = kzalloc(sizeof(*sub_pd), GFP_KERNEL);
+ if (!sub_pd) {
+ pr_err("%s %s: failed to allocate memory for power domain\n",
+ EXYNOS_PD_PREFIX, __func__);
+ return -ENOMEM;
+ }
+
+ sub_pd->name = kstrdup(children->name, GFP_KERNEL);
+ sub_pd->of_node = children;
+
+ /* Logical sub-domain does not have to power on/off control*/
+ sub_pd->pd_control = NULL;
+
+ sub_pd->devfreq_index = of_get_devfreq_sync_volt_idx(sub_pd->of_node);
+
+ /* kernel does not create sub-domain pdev. */
+ sub_pdev = of_find_device_by_node(children);
+ if (!sub_pdev)
+ sub_pdev = of_platform_device_create(children, NULL, &pdev->dev);
+ if (!sub_pdev) {
+ pr_err(EXYNOS_PD_PREFIX "sub domain allocation failed: %s\n", children->name);
+ continue;
+ }
+
+ mutex_init(&sub_pd->access_lock);
+ platform_set_drvdata(sub_pdev, sub_pd);
+
+ exynos_pd_genpd_init(sub_pd, initial_state);
+ of_genpd_add_provider_simple(children, &sub_pd->genpd);
+
+ if (pm_genpd_add_subdomain(&pd->genpd, &sub_pd->genpd))
+ pr_err("%s %s can't add subdomain %s\n",
+ EXYNOS_PD_PREFIX, pd->genpd.name, sub_pd->genpd.name);
+ else
+ pr_info(EXYNOS_PD_PREFIX "%s has a new logical child %s.\n",
+ pd->genpd.name, sub_pd->genpd.name);
+ }
+ }
+
+ /* EXCEPTION: add physical sub-pd to master pd using device tree */
+ for_each_compatible_node(np, NULL, "samsung,exynos-pd") {
+ struct exynos_pm_domain *parent_pd, *child_pd;
+ struct device_node *parent;
+ struct platform_device *parent_pd_pdev, *child_pd_pdev;
+ int i;
+
+ /* skip unmanaged power domain */
+ if (!of_device_is_available(np))
+ continue;
+
+ /* child_pd_pdev should have value. */
+ child_pd_pdev = of_find_device_by_node(np);
+ child_pd = platform_get_drvdata(child_pd_pdev);
+
+ /* search parents in device tree */
+ for (i = 0; i < MAX_PARENT_POWER_DOMAIN; i++) {
+ /* find parent node if available */
+ parent = of_parse_phandle(np, "parent", i);
+ if (!parent)
+ break;
+
+ /* display error when parent is unmanaged. */
+ if (!of_device_is_available(parent)) {
+ pr_err(EXYNOS_PD_PREFIX "%s is not managed by runtime pm.\n", parent->name);
+ continue;
+ }
+
+ /* parent_pd_pdev should have value. */
+ parent_pd_pdev = of_find_device_by_node(parent);
+ parent_pd = platform_get_drvdata(parent_pd_pdev);
+
+ if (pm_genpd_add_subdomain(&parent_pd->genpd, &child_pd->genpd))
+ pr_err(EXYNOS_PD_PREFIX "%s cannot add subdomain %s\n",
+ parent_pd->name, child_pd->name);
+ else
+ pr_info(EXYNOS_PD_PREFIX "%s has a new child %s.\n",
+ parent_pd->name, child_pd->name);
+ }
+ }
+
+ return 0;
+}
+#endif /* CONFIG_OF */
+
+static int __init exynos_pd_init(void)
+{
+ int ret;
+#ifdef CONFIG_OF
+ if (of_have_populated_dt()) {
+ ret = exynos_pd_dt_parse();
+ if (ret) {
+ pr_err(EXYNOS_PD_PREFIX "dt parse failed.\n");
+ return ret;
+ }
+
+ pr_info("%s PM Domain Initialize\n", EXYNOS_PD_PREFIX);
+ /* show information of power domain registration */
+ exynos_pd_show_power_domain();
+
+ return 0;
+ }
+#endif
+ pr_err(EXYNOS_PD_PREFIX "PM Domain works along with Device Tree\n");
+ return -EPERM;
+}
+subsys_initcall(exynos_pd_init);
--- /dev/null
+/*
+ * This header provides constants for Exynos PM Domain bindings.
+ *
+ * Copyright (C) 2016 Samsung Electronics Co., Ltd.
+ * Jeonghoon Jang <jnghn.jang@samsung.com>
+ *
+ * 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
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#ifndef _DT_BINDINGS_POWER_EXYNOS_POWER_H
+#define _DT_BINDINGS_POWER_EXYNOS_POWER_H
+
+#define PD_OK_AUD 0x1
+#define PD_OK_VTS 0x2
+
+#endif
--- /dev/null
+/* arch/arm64/mach-exynos/include/mach/apm-exynos.h
+ *
+ * Copyright (c) 2014 Samsung Electronics Co., Ltd.
+ * http://www.samsung.com
+ *
+ * APM register definitions
+ *
+ * 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 __ASM_ARCH_REGS_APM_H
+#define __ASM_ARCH_REGS_APM_H __FILE__
+
+#include <linux/regmap.h>
+#include <linux/types.h>
+#include <linux/file.h>
+#include <linux/fs.h>
+
+/* Margin related variables */
+#define MARGIN_0MV (0)
+#define MARGIN_6_25MV (1)
+#define MARGIN_12_5MV (2)
+#define MARGIN_18_75MV (3)
+#define MARGIN_25MV (4)
+#define MARGIN_31_25MV (5)
+#define MARGIN_37_5MV (6)
+#define MARGIN_43_75MV (7)
+#define MARGIN_50MV (8)
+#define MARGIN_56_25MV (9)
+#define MARGIN_62_5MV (0xA)
+#define MARGIN_68_75MV (0xB)
+#define MARGIN_75MV (0xC)
+#define MARGIN_81_25MV (0xD)
+#define MARGIN_87_5MV (0xE)
+#define MARGIN_93_75MV (0xF)
+
+/* PERIOD related variables */
+#define PERIOD_1MS (0)
+#define PERIOD_5MS (1)
+
+/* APM Protocol related variables */
+/* Notifier variables */
+#define APM_READY (0x0001)
+#define APM_SLEEP (0x0002)
+#define APM_TIMEOUT (0x0003)
+#define CL_ENABLE (0x0004)
+#define CL_DISABLE (0x0005)
+
+/* Shift variables */
+#define CL_DVFS_SHIFT (29)
+#define COMMAND_SHIFT (27)
+#define PM_SECTION_SHIFT (26)
+#define MASK_SHIFT (25)
+#define INIT_MODE_SHIFT (22)
+#define ASV_MODE_SHIFT (21)
+#define CL_ALL_STOP_SHIFT (30)
+#define CL_ALL_START_SHIFT (31)
+#define MULTI_BYTE_SHIFT (16)
+#define CL_DOMAIN_SHIFT (14)
+
+#define MULTI_BYTE_CNT_SHIFT (16)
+#define ATLAS_SHIFT (0)
+#define APOLLO_SHIFT (4)
+#define G3D_SHIFT (8)
+#define MIF_SHIFT (12)
+#define PERIOD_SHIFT (16)
+#define BYTE_SHIFT (8)
+#define BYTE_MASK (0xFF)
+#define WRITE_MODE (0)
+#define READ_MODE (1)
+#define NONE (2)
+#define TX_INTERRUPT_ENABLE (1)
+#define MASK (1)
+#define BYTE_4 (4)
+#define INIT_SET (1)
+#define ASV_SET (1)
+#define DEBUG_COUNT (10)
+
+/* Mask variables */
+#define CL_DVFS_MASK (1)
+#define COMMAND_MASK (0x3)
+#define MULTI_BYTE_MASK (0xF)
+#define CL_DVFS (CL_DVFS_MASK << CL_DVFS_SHIFT)
+#define CL_DVFS_OFF (0)
+#define COMMAND (COMMAND_MASK << COMMAND_SHIFT)
+#define MULTI_BYTE (MULTI_BYTE_MASK << MULTI_BYTE_SHIFT)
+
+/* Error variable */
+#define APM_RET_SUCESS (0xa)
+#define APM_GPIO_ERR (0xFFFFFFFF)
+#define PMIC_NO_ACK_ERR (0xEEEEEEEE)
+#define ERR_TIMEOUT (1)
+#define ERR_RETRY (2)
+#define ERR_OUT (3)
+#define RETRY_ERR (-0xFF)
+
+/* apm related variables */
+#define MSG_LEN (5)
+#define G3D_LV_OFFSET (0)
+#define MBOX_LEN (4)
+#define TIMEOUT (500) /* timeout 500 msec */
+#define TX (0)
+#define RX (1)
+#define HSI2C_MODE (0)
+#define APM_MODE (1)
+#define APM_TIMOUT (2)
+
+#define CL_ON (1)
+#define CL_OFF (0)
+
+#define APM_OFF (0)
+#define APM_ON (1)
+#define APM_WFI_TIMEOUT (2)
+
+#define EXYNOS_PMU_CORTEXM3_APM_CONFIGURATION (0x2500)
+#define EXYNOS_PMU_CORTEXM3_APM_STATUS (0x2504)
+#define EXYNOS_PMU_CORTEXM3_APM_OPTION (0x2508)
+#define EXYNOS_PMU_CORTEXM3_APM_DURATION0 (0x2510)
+#define EXYNOS_PMU_CORTEXM3_APM_DURATION1 (0x2514)
+#define EXYNOS_PMU_CORTEXM3_APM_DURATION2 (0x2518)
+#define EXYNOS_PMU_CORTEXM3_APM_DURATION3 (0x251C)
+
+/* CORTEX M3 */
+#define ENABLE_APM (0x1 << 15)
+#define APM_STATUS_MASK (0x1)
+#define STANDBYWFI (28)
+#define STANDBYWFI_MASK (0x1)
+#define APM_LOCAL_PWR_CFG_RUN (0x1 << 0)
+#define APM_LOCAL_PWR_CFG_RESET (~(0x1 << 0))
+
+struct apm_ops {
+ int (*apm_update_bits) (unsigned int type, unsigned int reg,
+ unsigned int mask, unsigned int value);
+ int (*apm_write) (unsigned int type, unsigned int reg, unsigned int value);
+ int (*apm_bulk_write) (unsigned int type, unsigned char reg,
+ unsigned char *buf, unsigned int count);
+ int (*apm_read) (unsigned int type, unsigned int reg, unsigned int *val);
+ int (*apm_bulk_read) (unsigned int type, unsigned char reg,
+ unsigned char *buf, unsigned int count);
+};
+
+struct cl_ops {
+ void (*apm_reset) (void);
+ void (*apm_power_up) (void);
+ void (*apm_power_down) (void);
+ int (*cl_dvfs_setup) (unsigned int atlas_cl_limit, unsigned int apollo_cl_limit,
+ unsigned int g3d_cl_limit, unsigned int mif_cl_limit, unsigned int cl_period);
+ int (*cl_dvfs_start) (unsigned int cl_domain);
+ int (*cl_dvfs_stop) (unsigned int cl_domain, unsigned int level);
+ int (*cl_dvfs_enable) (void);
+ int (*cl_dvfs_disable) (void);
+ int (*g3d_power_on) (void);
+ int (*g3d_power_down) (void);
+ int (*enter_wfi) (void);
+};
+
+struct debug_data {
+ u32 buf[DEBUG_COUNT][6];
+ s64 time[DEBUG_COUNT];
+ char* name[DEBUG_COUNT];
+ unsigned int cnt;
+#ifdef CONFIG_EXYNOS_APM_VOLTAGE_DEBUG
+ u32 vol[DEBUG_COUNT][4];
+ u32 atl_value;
+ u32 apo_value;
+ u32 g3d_value;
+ u32 mif_value;
+#endif
+};
+
+struct cl_init_data {
+ u32 atlas_margin;
+ u32 apollo_margin;
+ u32 g3d_margin;
+ u32 mif_margin;
+ u32 period;
+ u32 cl_status;
+ u32 apm_status;
+};
+
+extern void cl_dvfs_lock(void);
+extern void cl_dvfs_unlock(void);
+extern int cm3_status_open(struct inode *inode, struct file *file);
+extern struct apm_ops exynos_apm_function_ops;
+
+extern int register_apm_notifier(struct notifier_block *nb);
+extern int unregister_apm_notifier(struct notifier_block *nb);
+extern int apm_notifier_call_chain(unsigned long val);
+extern void exynos_apm_reset_release(void);
+extern void exynos_apm_power_up(void);
+extern void exynos_apm_power_down(void);
+extern int exynos_cl_dvfs_setup(unsigned int atlas_cl_limit, unsigned int apollo_cl_limit, unsigned int g3d_cl_limit,
+ unsigned int mif_cl_limit, unsigned int cl_period);
+extern int exynos_cl_dvfs_start(unsigned int cl_domain);
+extern int exynos_cl_dvfs_stop(unsigned int cl_domain, unsigned int level);
+extern int exynos_cl_dvfs_mode_enable(void);
+extern int exynos_cl_dvfs_mode_disable(void);
+extern int exynos_g3d_power_on_noti_apm(void);
+extern int exynos_g3d_power_down_noti_apm(void);
+extern int exynos_apm_enter_wfi(void);
+extern int exynos_apm_update_bits(unsigned int type, unsigned int reg, unsigned int mask, unsigned int value);
+extern int exynos_apm_write(unsigned int type, unsigned int address, unsigned int value);
+extern int exynos_apm_bulk_write(unsigned int type, unsigned char reg, unsigned char *buf, unsigned int count);
+extern int exynos_apm_read(unsigned int type, unsigned int reg, unsigned int *val);
+extern int exynos_apm_bulk_read(unsigned int type, unsigned char reg, unsigned char *buf, unsigned int count);
+
+#define exynos7890_cl_dvfs_start(a) exynos7420_cl_dvfs_start(a)
+#define exynos7890_cl_dvfs_stop(a, b) exynos7420_cl_dvfs_stop(a, b)
+#define exynos7890_cl_dvfs_mode_enable() exynos7420_cl_dvfs_mode_enable()
+#define exynos7890_cl_dvfs_mode_disable() exynos7420_cl_dvfs_mode_disable()
+#define exynos7890_g3d_power_on_noti_apm() exynos7420_g3d_power_on_noti_apm()
+#define exynos7890_g3d_power_down_noti_apm() exynos7420_g3d_power_down_noti_apm()
+#define exynos7890_apm_enter_wfi() exynos7420_apm_enter_wfi()
+#define exynos7890_apm_update_bits(a, b, c, d) exynos7420_apm_update_bits(a, b, c, d)
+#define exynos7890_apm_write(a, b, c) exynos7420_apm_write(a, b, c)
+#define exynos7890_apm_bulk_write(a, b, c, d) exynos7420_apm_bulk_write(a, b, c, d)
+#define exynos7890_apm_read(a, b, c) exynos7420_apm_read(a, b, c)
+#define exynos7890_apm_bulk_read(a, b, c, d) exynos7420_apm_bulk_read(a, b, c, d)
+
+unsigned int exynos_cortexm3_pmu_read(unsigned int offset);
+void exynos_cortexm3_pmu_write(unsigned int val, unsigned int offset);
+unsigned int exynos_mailbox_reg_read(unsigned int offset);
+void exynos_mailbox_reg_write(unsigned int val, unsigned int offset);
+#endif
--- /dev/null
+/*
+ * Exynos PM domain support.
+ *
+ * Copyright (c) 2016 Samsung Electronics Co., Ltd.
+ * http://www.samsung.com
+ *
+ * Implementation of Exynos specific power domain control which is used in
+ * conjunction with runtime-pm. Support for both device-tree and non-device-tree
+ * based power domain support is included.
+ *
+ * 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 __EXYNOS_PD_H
+#define __EXYNOS_PD_H __FILE__
+
+#include <linux/io.h>
+#include <linux/err.h>
+#include <linux/delay.h>
+#include <linux/of_address.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/pm_domain.h>
+#include <linux/pm_runtime.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/mutex.h>
+#include <linux/debugfs.h>
+
+#include <linux/mfd/samsung/core.h>
+#include <soc/samsung/bcm.h>
+
+#include <soc/samsung/exynos-powermode.h>
+#include <soc/samsung/exynos-devfreq.h>
+#include <dt-bindings/power/exynos-power.h>
+
+#define EXYNOS_PD_PREFIX "EXYNOS-PD: "
+#define EXYNOS_PD_DBG_PREFIX "EXYNOS-PD-DBG: "
+
+#ifndef pr_fmt
+#define pr_fmt(fmt) fmt
+#endif
+
+#ifdef CONFIG_EXYNOS_PM_DOMAIN_DEBUG
+#define DEBUG_PRINT_INFO(fmt, ...) printk(PM_DOMAIN_PREFIX pr_fmt(fmt), ##__VA_ARGS__)
+#else
+#define DEBUG_PRINT_INFO(fmt, ...)
+#endif
+
+/* In Exynos, the number of MAX_POWER_DOMAIN is less than 15 */
+#define MAX_PARENT_POWER_DOMAIN 15
+
+struct exynos_pm_domain;
+
+struct exynos_pm_domain {
+ struct generic_pm_domain genpd;
+ char *name;
+ unsigned int cal_pdid;
+ struct device_node *of_node;
+ int (*pd_control)(unsigned int cal_id, int on);
+ int (*check_status)(struct exynos_pm_domain *pd);
+ bool (*power_down_ok)(void);
+ unsigned int bts;
+ int devfreq_index;
+ struct mutex access_lock;
+ int idle_ip_index;
+#if defined(CONFIG_EXYNOS_BCM)
+ struct bcm_info *bcm;
+#endif
+ bool power_down_skipped;
+ unsigned int need_smc;
+};
+
+struct exynos_pd_dbg_info {
+ struct device *dev;
+#ifdef CONFIG_DEBUG_FS
+ struct dentry *d;
+ struct file_operations fops;
+#endif
+};
+
+#ifdef CONFIG_EXYNOS_PD
+struct exynos_pm_domain *exynos_pd_lookup_name(const char *domain_name);
+#else
+static inline struct exynos_pm_domain *exynos_pd_lookup_name(const char *domain_name)
+{
+ return NULL;
+}
+#endif
+
+#ifdef CONFIG_SND_SOC_SAMSUNG_VTS
+extern bool vts_is_on(void);
+#endif
+#ifdef CONFIG_SND_SOC_SAMSUNG_ABOX
+extern bool abox_is_on(void);
+#endif
+
+#endif /* __EXYNOS_PD_H */