From: yi jaeuk Date: Wed, 9 May 2018 08:10:19 +0000 (+0900) Subject: [9610] drivers: soc: introduce exynos_pd X-Git-Url: https://git.stricted.de/?a=commitdiff_plain;h=bc40001a3637da2fce655827306076bc6a3753aa;p=GitHub%2FLineageOS%2Fandroid_kernel_motorola_exynos9610.git [9610] drivers: soc: introduce exynos_pd Change-Id: Ia5ae988037a35136d60c7310e725f05d720f76fa Signed-off-by: yi jaeuk --- diff --git a/arch/arm64/boot/dts/exynos/exynos9610-pm-domains.dtsi b/arch/arm64/boot/dts/exynos/exynos9610-pm-domains.dtsi new file mode 100644 index 000000000000..9601d63a63e0 --- /dev/null +++ b/arch/arm64/boot/dts/exynos/exynos9610-pm-domains.dtsi @@ -0,0 +1,125 @@ +/* + * 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 + +/ { + 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_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>; + }; + +}; diff --git a/arch/arm64/boot/dts/exynos/exynos9610.dtsi b/arch/arm64/boot/dts/exynos/exynos9610.dtsi index f89b523cd901..9de3646e1256 100644 --- a/arch/arm64/boot/dts/exynos/exynos9610.dtsi +++ b/arch/arm64/boot/dts/exynos/exynos9610.dtsi @@ -21,6 +21,7 @@ #include #include "exynos9610-sysmmu.dtsi" #include +#include "exynos9610-pm-domains.dtsi" / { compatible = "samsung,armv8", "samsung,exynos9610"; diff --git a/drivers/soc/samsung/Kconfig b/drivers/soc/samsung/Kconfig index 0893705834a2..9cb1a39bdc51 100644 --- a/drivers/soc/samsung/Kconfig +++ b/drivers/soc/samsung/Kconfig @@ -104,4 +104,10 @@ config EXYNOS_DVFS_MANAGER 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 diff --git a/drivers/soc/samsung/Makefile b/drivers/soc/samsung/Makefile index e77f64c8ff2b..acca1607e296 100644 --- a/drivers/soc/samsung/Makefile +++ b/drivers/soc/samsung/Makefile @@ -14,7 +14,6 @@ obj-$(CONFIG_ARCH_EXYNOS) += exynos-fsys0-tcxo.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 obj-$(CONFIG_EXYNOS_CHIPID) += exynos-chipid.o @@ -29,3 +28,7 @@ obj-$(CONFIG_EXYNOS_REBOOT) += exynos-reboot.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 diff --git a/drivers/soc/samsung/exynos-pd-dbg.c b/drivers/soc/samsung/exynos-pd-dbg.c new file mode 100644 index 000000000000..f36421e7daa8 --- /dev/null +++ b/drivers/soc/samsung/exynos-pd-dbg.c @@ -0,0 +1,303 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include + +#include + +#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); diff --git a/drivers/soc/samsung/exynos-pd.c b/drivers/soc/samsung/exynos-pd.c new file mode 100644 index 000000000000..997a7fa1d88b --- /dev/null +++ b/drivers/soc/samsung/exynos-pd.c @@ -0,0 +1,469 @@ +/* + * 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 +#include +#include +#include +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); diff --git a/include/dt-bindings/power/exynos-power.h b/include/dt-bindings/power/exynos-power.h new file mode 100644 index 000000000000..47d05234a1d7 --- /dev/null +++ b/include/dt-bindings/power/exynos-power.h @@ -0,0 +1,19 @@ +/* + * This header provides constants for Exynos PM Domain bindings. + * + * Copyright (C) 2016 Samsung Electronics Co., Ltd. + * Jeonghoon Jang + * + * 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 diff --git a/include/linux/apm-exynos.h b/include/linux/apm-exynos.h new file mode 100644 index 000000000000..21ec51c42423 --- /dev/null +++ b/include/linux/apm-exynos.h @@ -0,0 +1,226 @@ +/* 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 +#include +#include +#include + +/* 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 diff --git a/include/soc/samsung/exynos-pd.h b/include/soc/samsung/exynos-pd.h new file mode 100644 index 000000000000..51247ca470ab --- /dev/null +++ b/include/soc/samsung/exynos-pd.h @@ -0,0 +1,99 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#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 */