blob: 440e31737d944981c5267ac42bbbda60c0b68bbe [file] [log] [blame]
/*
* 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_genpd_lock_spin(struct generic_pm_domain *genpd)
__acquires(&genpd->slock)
{
unsigned long flags;
spin_lock_irqsave(&genpd->slock, flags);
genpd->lock_flags = flags;
}
static void exynos_pd_dbg_genpd_unlock_spin(struct generic_pm_domain *genpd)
__releases(&genpd->slock)
{
spin_unlock_irqrestore(&genpd->slock, genpd->lock_flags);
}
static void exynos_pd_dbg_genpd_lock(struct generic_pm_domain *genpd)
{
if (genpd->flags & GENPD_FLAG_IRQ_SAFE)
exynos_pd_dbg_genpd_lock_spin(genpd);
else
mutex_lock(&genpd->mlock);
}
static void exynos_pd_dbg_genpd_unlock(struct generic_pm_domain *genpd)
{
if (genpd->flags & GENPD_FLAG_IRQ_SAFE)
exynos_pd_dbg_genpd_unlock_spin(genpd);
else
mutex_unlock(&genpd->mlock);
}
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;
exynos_pd_dbg_genpd_lock(genpd);
if (genpd->status >= ARRAY_SIZE(gpd_status_lookup)) {
pr_err("%s invalid GPD_STATUS\n", EXYNOS_PD_DBG_PREFIX);
exynos_pd_dbg_genpd_unlock(genpd);
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);
exynos_pd_dbg_genpd_unlock(genpd);
}
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);