linux/drivers/base/power/opp/debugfs.c
<<
>>
Prefs
   1/*
   2 * Generic OPP debugfs interface
   3 *
   4 * Copyright (C) 2015-2016 Viresh Kumar <viresh.kumar@linaro.org>
   5 *
   6 * This program is free software; you can redistribute it and/or modify
   7 * it under the terms of the GNU General Public License version 2 as
   8 * published by the Free Software Foundation.
   9 */
  10
  11#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
  12
  13#include <linux/debugfs.h>
  14#include <linux/device.h>
  15#include <linux/err.h>
  16#include <linux/init.h>
  17#include <linux/limits.h>
  18
  19#include "opp.h"
  20
  21static struct dentry *rootdir;
  22
  23static void opp_set_dev_name(const struct device *dev, char *name)
  24{
  25        if (dev->parent)
  26                snprintf(name, NAME_MAX, "%s-%s", dev_name(dev->parent),
  27                         dev_name(dev));
  28        else
  29                snprintf(name, NAME_MAX, "%s", dev_name(dev));
  30}
  31
  32void opp_debug_remove_one(struct dev_pm_opp *opp)
  33{
  34        debugfs_remove_recursive(opp->dentry);
  35}
  36
  37int opp_debug_create_one(struct dev_pm_opp *opp, struct opp_table *opp_table)
  38{
  39        struct dentry *pdentry = opp_table->dentry;
  40        struct dentry *d;
  41        char name[25];  /* 20 chars for 64 bit value + 5 (opp:\0) */
  42
  43        /* Rate is unique to each OPP, use it to give opp-name */
  44        snprintf(name, sizeof(name), "opp:%lu", opp->rate);
  45
  46        /* Create per-opp directory */
  47        d = debugfs_create_dir(name, pdentry);
  48        if (!d)
  49                return -ENOMEM;
  50
  51        if (!debugfs_create_bool("available", S_IRUGO, d, &opp->available))
  52                return -ENOMEM;
  53
  54        if (!debugfs_create_bool("dynamic", S_IRUGO, d, &opp->dynamic))
  55                return -ENOMEM;
  56
  57        if (!debugfs_create_bool("turbo", S_IRUGO, d, &opp->turbo))
  58                return -ENOMEM;
  59
  60        if (!debugfs_create_bool("suspend", S_IRUGO, d, &opp->suspend))
  61                return -ENOMEM;
  62
  63        if (!debugfs_create_ulong("rate_hz", S_IRUGO, d, &opp->rate))
  64                return -ENOMEM;
  65
  66        if (!debugfs_create_ulong("u_volt_target", S_IRUGO, d, &opp->u_volt))
  67                return -ENOMEM;
  68
  69        if (!debugfs_create_ulong("u_volt_min", S_IRUGO, d, &opp->u_volt_min))
  70                return -ENOMEM;
  71
  72        if (!debugfs_create_ulong("u_volt_max", S_IRUGO, d, &opp->u_volt_max))
  73                return -ENOMEM;
  74
  75        if (!debugfs_create_ulong("u_amp", S_IRUGO, d, &opp->u_amp))
  76                return -ENOMEM;
  77
  78        if (!debugfs_create_ulong("clock_latency_ns", S_IRUGO, d,
  79                                  &opp->clock_latency_ns))
  80                return -ENOMEM;
  81
  82        opp->dentry = d;
  83        return 0;
  84}
  85
  86static int opp_list_debug_create_dir(struct opp_device *opp_dev,
  87                                     struct opp_table *opp_table)
  88{
  89        const struct device *dev = opp_dev->dev;
  90        struct dentry *d;
  91
  92        opp_set_dev_name(dev, opp_table->dentry_name);
  93
  94        /* Create device specific directory */
  95        d = debugfs_create_dir(opp_table->dentry_name, rootdir);
  96        if (!d) {
  97                dev_err(dev, "%s: Failed to create debugfs dir\n", __func__);
  98                return -ENOMEM;
  99        }
 100
 101        opp_dev->dentry = d;
 102        opp_table->dentry = d;
 103
 104        return 0;
 105}
 106
 107static int opp_list_debug_create_link(struct opp_device *opp_dev,
 108                                      struct opp_table *opp_table)
 109{
 110        const struct device *dev = opp_dev->dev;
 111        char name[NAME_MAX];
 112        struct dentry *d;
 113
 114        opp_set_dev_name(opp_dev->dev, name);
 115
 116        /* Create device specific directory link */
 117        d = debugfs_create_symlink(name, rootdir, opp_table->dentry_name);
 118        if (!d) {
 119                dev_err(dev, "%s: Failed to create link\n", __func__);
 120                return -ENOMEM;
 121        }
 122
 123        opp_dev->dentry = d;
 124
 125        return 0;
 126}
 127
 128/**
 129 * opp_debug_register - add a device opp node to the debugfs 'opp' directory
 130 * @opp_dev: opp-dev pointer for device
 131 * @opp_table: the device-opp being added
 132 *
 133 * Dynamically adds device specific directory in debugfs 'opp' directory. If the
 134 * device-opp is shared with other devices, then links will be created for all
 135 * devices except the first.
 136 *
 137 * Return: 0 on success, otherwise negative error.
 138 */
 139int opp_debug_register(struct opp_device *opp_dev, struct opp_table *opp_table)
 140{
 141        if (!rootdir) {
 142                pr_debug("%s: Uninitialized rootdir\n", __func__);
 143                return -EINVAL;
 144        }
 145
 146        if (opp_table->dentry)
 147                return opp_list_debug_create_link(opp_dev, opp_table);
 148
 149        return opp_list_debug_create_dir(opp_dev, opp_table);
 150}
 151
 152static void opp_migrate_dentry(struct opp_device *opp_dev,
 153                               struct opp_table *opp_table)
 154{
 155        struct opp_device *new_dev;
 156        const struct device *dev;
 157        struct dentry *dentry;
 158
 159        /* Look for next opp-dev */
 160        list_for_each_entry(new_dev, &opp_table->dev_list, node)
 161                if (new_dev != opp_dev)
 162                        break;
 163
 164        /* new_dev is guaranteed to be valid here */
 165        dev = new_dev->dev;
 166        debugfs_remove_recursive(new_dev->dentry);
 167
 168        opp_set_dev_name(dev, opp_table->dentry_name);
 169
 170        dentry = debugfs_rename(rootdir, opp_dev->dentry, rootdir,
 171                                opp_table->dentry_name);
 172        if (!dentry) {
 173                dev_err(dev, "%s: Failed to rename link from: %s to %s\n",
 174                        __func__, dev_name(opp_dev->dev), dev_name(dev));
 175                return;
 176        }
 177
 178        new_dev->dentry = dentry;
 179        opp_table->dentry = dentry;
 180}
 181
 182/**
 183 * opp_debug_unregister - remove a device opp node from debugfs opp directory
 184 * @opp_dev: opp-dev pointer for device
 185 * @opp_table: the device-opp being removed
 186 *
 187 * Dynamically removes device specific directory from debugfs 'opp' directory.
 188 */
 189void opp_debug_unregister(struct opp_device *opp_dev,
 190                          struct opp_table *opp_table)
 191{
 192        if (opp_dev->dentry == opp_table->dentry) {
 193                /* Move the real dentry object under another device */
 194                if (!list_is_singular(&opp_table->dev_list)) {
 195                        opp_migrate_dentry(opp_dev, opp_table);
 196                        goto out;
 197                }
 198                opp_table->dentry = NULL;
 199        }
 200
 201        debugfs_remove_recursive(opp_dev->dentry);
 202
 203out:
 204        opp_dev->dentry = NULL;
 205}
 206
 207static int __init opp_debug_init(void)
 208{
 209        /* Create /sys/kernel/debug/opp directory */
 210        rootdir = debugfs_create_dir("opp", NULL);
 211        if (!rootdir) {
 212                pr_err("%s: Failed to create root directory\n", __func__);
 213                return -ENOMEM;
 214        }
 215
 216        return 0;
 217}
 218core_initcall(opp_debug_init);
 219