Commit 866c74bf7df1c53b283c45aee1a6105361a9bb47

Authored by Vishwanath BS
Committed by Afzal Mohammed
1 parent 8d9459b2cf
Exists in master

OMAP3+: PM: DVFS: introduce helper functions for DVFS

This patch introduces helper functions for Device Voltage and Frequency
Scaling (DVFS).
Data structures added:
1. omap_dev_user_list: This structure maintains list of frequency requests per
   device basis. When a device needs to be scaled to a particular frequency,
   This list is searched to find the maximum request for a given device.
   If noone has placed any request, device frequency is obtained from device
   opp table.
2. omap_vdd_dev_list: This strcucture stores device list per vdd basis.
   Whenever a device is registered with a vdd, it is added to this list.
3. omap_vdd_user_list: User list of devices associated with each voltage domain
   instance. The user list is implemented using plist structure with priority
   node populated with the voltage values.
4. omap_vdd_dvfs_info: This structure is used to abstract DVFS related
   information per VDD basis. It holds pointer to corresponding vdd's
   voltagedomain instance and pointer to user list.

Following helper functions have been added to operate on above data structures:
1. omap_dvfs_add_vdd_user - function to add a user into omap_vdd_user_list
2. omap_vdd_user_list - function to remove a user from omap_vdd_user_list
3. omap_dvfs_register_device - function to register a device with vdd
4. omap_dvfs_add_freq_request - function to add a frequency request into
   omap_dev_user_list
5. omap_dvfs_remove_freq_request - function to remove a frequency request from
   omap_dev_user_list
6. omap_dvfs_find_voltage - function to find the opp corresponding to given
   voltage
7. omap_device_scale - this is the function that is to be used for scaling
   a specific domain. This is exported for drivers such as syslink, dspbridge
   to use it while being a module.

DVFS layer is initialized and basic data structures are allocated and
initialized as part of this.

This patch is based on Vishwa and Thara's previous DVFS implementation, but with
major rework.

note: generates "warning: 'omap_dvfs_remove_vdd_user' defined but not used"
this function will be used in future patches

[nm@ti.com: misc cleanups including use voltage files from mach-omap2 dir]
[axelhaslam@ti.com: fixed issue with ondemand - required mutex unlock]
[toddpoynor@google.com: Add spinlocking of plists, GFP_ATOMIC]
Signed-off-by: Nishanth Menon <nm@ti.com>
Signed-off-by: Axel Haslam <axelhaslam@ti.com>
Signed-off-by: Todd Poynor <toddpoynor@google.com>
Signed-off-by: Vishwanath BS <vishwanath.bs@ti.com>
Cc: Thara Gopinath <thara@ti.com>
[vaibhav.bedia@ti.com: Pull in for AM33xx]
Signed-off-by: Vaibhav Bedia <vaibhav.bedia@ti.com>
[afzal@ti.com: fix warning]
Signed-off-by: Afzal Mohammed <afzal@ti.com>

Showing 3 changed files with 1018 additions and 2 deletions Side-by-side Diff

arch/arm/mach-omap2/Makefile
... ... @@ -80,7 +80,7 @@
80 80 # PRCM
81 81 obj-$(CONFIG_ARCH_OMAP2) += prcm.o cm2xxx_3xxx.o prm2xxx_3xxx.o
82 82 obj-$(CONFIG_ARCH_OMAP3) += prcm.o cm2xxx_3xxx.o prm2xxx_3xxx.o \
83   - vc3xxx_data.o vp3xxx_data.o
  83 + vc3xxx_data.o vp3xxx_data.o dvfs.o
84 84 obj-$(CONFIG_SOC_OMAPAM33XX) += cminst33xx.o prminst33xx.o cm33xx.o
85 85  
86 86 # XXX The presence of cm2xxx_3xxx.o on the line below is temporary and
... ... @@ -89,7 +89,7 @@
89 89 obj-$(CONFIG_ARCH_OMAP4) += prcm.o cm2xxx_3xxx.o cminst44xx.o \
90 90 cm44xx.o prcm_mpu44xx.o \
91 91 prminst44xx.o vc44xx_data.o \
92   - vp44xx_data.o
  92 + vp44xx_data.o dvfs.o
93 93  
94 94 # OMAP voltage domains
95 95 voltagedomain-common := voltage.o vc.o vp.o
arch/arm/mach-omap2/dvfs.c
  1 +/*
  2 + * OMAP3/OMAP4 DVFS Management Routines
  3 + *
  4 + * Author: Vishwanath BS <vishwanath.bs@ti.com>
  5 + *
  6 + * Copyright (C) 2011 Texas Instruments, Inc.
  7 + * Vishwanath BS <vishwanath.bs@ti.com>
  8 + *
  9 + * This program is free software; you can redistribute it and/or modify
  10 + * it under the terms of the GNU General Public License version 2 as
  11 + * published by the Free Software Foundation.
  12 + */
  13 +
  14 +#include <linux/err.h>
  15 +#include <linux/spinlock.h>
  16 +#include <linux/plist.h>
  17 +#include <linux/slab.h>
  18 +#include <linux/opp.h>
  19 +#include <linux/clk.h>
  20 +#include <plat/common.h>
  21 +#include <plat/omap_device.h>
  22 +#include <plat/omap_hwmod.h>
  23 +#include <plat/clock.h>
  24 +#include "dvfs.h"
  25 +#include "smartreflex.h"
  26 +#include "powerdomain.h"
  27 +
  28 +/**
  29 + * DOC: Introduction
  30 + * =================
  31 + * DVFS is a technique that uses the optimal operating frequency and voltage to
  32 + * allow a task to be performed in the required amount of time.
  33 + * OMAP processors have voltage domains whose voltage can be scaled to
  34 + * various levels depending on which the operating frequencies of certain
  35 + * devices belonging to the domain will also need to be scaled. This voltage
  36 + * frequency tuple is known as Operating Performance Point (OPP). A device
  37 + * can have multiple OPP's. Also a voltage domain could be shared between
  38 + * multiple devices. Also there could be dependencies between various
  39 + * voltage domains for maintaining system performance like VDD<X>
  40 + * should be at voltage v1 when VDD<Y> is at voltage v2.
  41 + *
  42 + * The design of this framework takes into account all the above mentioned
  43 + * points. To summarize the basic design of DVFS framework:-
  44 + *
  45 + * 1. Have device opp tables for each device whose operating frequency can be
  46 + * scaled. This is easy now due to the existance of hwmod layer which
  47 + * allow storing of device specific info. The device opp tables contain
  48 + * the opp pairs (frequency voltage tuples), the voltage domain pointer
  49 + * to which the device belongs to, the device specific set_rate and
  50 + * get_rate API's which will do the actual scaling of the device frequency
  51 + * and retrieve the current device frequency.
  52 + * 2. Introduce use counting on a per VDD basis. This is to take care multiple
  53 + * requests to scale a VDD. The VDD will be scaled to the maximum of the
  54 + * voltages requested.
  55 + * 3. Keep track of all scalable devices belonging to a particular voltage
  56 + * domain the voltage layer.
  57 + * 4. Keep track of frequency requests for each of the device. This will enable
  58 + * to scale individual devices to different frequency (even w/o scaling
  59 + * voltage aka frequency throttling)
  60 + * 5. Generic dvfs API that can be called by anybody to scale a device opp.
  61 + * This API takes the device pointer and frequency to which the device
  62 + * needs to be scaled to. This API then internally finds out the voltage
  63 + * domain to which the device belongs to and the voltage to which the voltage
  64 + * domain needs to be put to for the device to be scaled to the new frequency
  65 + * from he device opp table. Then this API will add requested frequency into
  66 + * the corresponding target device frequency list and add voltage request to
  67 + * the corresponding vdd. Subsequently it calls voltage scale function which
  68 + * will find out the highest requested voltage for the given vdd and scales
  69 + * the voltage to the required one. It also runs through the list of all
  70 + * scalable devices belonging to this voltage domain and scale them to the
  71 + * appropriate frequencies using the set_rate pointer in the device opp
  72 + * tables.
  73 + * 6. Handle inter VDD dependecies.
  74 + *
  75 + *
  76 + * DOC: The Core DVFS data structure:
  77 + * ==================================
  78 + * Structure Name Example Tree
  79 + * ---------
  80 + * /|\ +-------------------+ +-------------------+
  81 + * | |User2 (dev2, freq2)+---\ |User4 (dev4, freq4)+---\
  82 + * | +-------------------+ | +-------------------+ |
  83 + * (struct omap_dev_user_list) | |
  84 + * | +-------------------+ | +-------------------+ |
  85 + * | |User1 (dev1, freq1)+---| |User3 (dev3, freq3)+---|
  86 + * \|/ +-------------------+ | +-------------------+ |
  87 + * --------- | |
  88 + * /|\ +------------+------+ +---------------+--+
  89 + * | | DEV1 (dev, | | DEV2 (dev) |
  90 + * (struct omap_vdd_dev_list)|omap_dev_user_list)| |omap_dev_user_list|
  91 + * | +------------+------+ +--+---------------+
  92 + * \|/ /|\ /-----+-------------+------> others..
  93 + * --------- Frequency |
  94 + * /|\ +--+------------------+
  95 + * | | VDD_n |
  96 + * | | (omap_vdd_dev_list, |
  97 + * (struct omap_vdd_dvfs_info)** | omap_vdd_user_list) |
  98 + * | +--+------------------+
  99 + * | | (ROOT NODE: omap_dvfs_info_list)
  100 + * \|/ |
  101 + * --------- Voltage \---+-------------+----------> others..
  102 + * /|\ \|/ +-------+----+ +-----+--------+
  103 + * | | vdd_user2 | | vdd_user3 |
  104 + * (struct omap_vdd_user_list) | (dev, volt)| | (dev, volt) |
  105 + * \|/ +------------+ +--------------+
  106 + * ---------
  107 + * Key: ** -> Root of the tree.
  108 + * NOTE: we use the priority to store the voltage/frequency
  109 + *
  110 + * For voltage dependency description, see: struct dependency:
  111 + * voltagedomain -> (description of the voltagedomain)
  112 + * omap_vdd_info -> (vdd information)
  113 + * omap_vdd_dep_info[]-> (stores array of depedency info)
  114 + * omap_vdd_dep_volt[] -> (stores array of maps)
  115 + * (main_volt -> dep_volt) (a singular map)
  116 + */
  117 +
  118 +/* Macros to give idea about scaling directions */
  119 +#define DVFS_VOLT_SCALE_DOWN 0
  120 +#define DVFS_VOLT_SCALE_NONE 1
  121 +#define DVFS_VOLT_SCALE_UP 2
  122 +
  123 +/**
  124 + * struct omap_dev_user_list - Structure maitain userlist per devide
  125 + * @dev: The device requesting for a particular frequency
  126 + * @node: The list head entry
  127 + *
  128 + * Using this structure, user list (requesting dev * and frequency) for
  129 + * each device is maintained. This is how we can have different devices
  130 + * at different frequencies (to support frequency locking and throttling).
  131 + * Even if one of the devices in a given vdd has locked it's frequency,
  132 + * other's can still scale their frequency using this list.
  133 + * If no one has placed a frequency request for a device, then device is
  134 + * set to the frequency from it's opp table.
  135 + */
  136 +struct omap_dev_user_list {
  137 + struct device *dev;
  138 + struct plist_node node;
  139 +};
  140 +
  141 +/**
  142 + * struct omap_vdd_dev_list - Device list per vdd
  143 + * @dev: The device belonging to a particular vdd
  144 + * @node: The list head entry
  145 + * @freq_user_list: The list of users for vdd device
  146 + * @clk: frequency control clock for this dev
  147 + * @user_lock: The lock for plist manipulation
  148 + */
  149 +struct omap_vdd_dev_list {
  150 + struct device *dev;
  151 + struct list_head node;
  152 + struct plist_head freq_user_list;
  153 + struct clk *clk;
  154 + spinlock_t user_lock; /* spinlock for plist */
  155 +};
  156 +
  157 +/**
  158 + * struct omap_vdd_user_list - The per vdd user list
  159 + * @dev: The device asking for the vdd to be set at a particular
  160 + * voltage
  161 + * @node: The list head entry
  162 + */
  163 +struct omap_vdd_user_list {
  164 + struct device *dev;
  165 + struct plist_node node;
  166 +};
  167 +
  168 +/**
  169 + * struct omap_vdd_dvfs_info - The per vdd dvfs info
  170 + * @node: list node for vdd_dvfs_info list
  171 + * @user_lock: spinlock for plist operations
  172 + * @vdd_user_list: The vdd user list
  173 + * @voltdm: Voltage domains for which dvfs info stored
  174 + * @dev_list: Device list maintained per domain
  175 + *
  176 + * This is a fundamental structure used to store all the required
  177 + * DVFS related information for a vdd.
  178 + */
  179 +struct omap_vdd_dvfs_info {
  180 + struct list_head node;
  181 +
  182 + spinlock_t user_lock; /* spin lock */
  183 + struct plist_head vdd_user_list;
  184 + struct voltagedomain *voltdm;
  185 + struct list_head dev_list;
  186 +};
  187 +
  188 +static LIST_HEAD(omap_dvfs_info_list);
  189 +static DEFINE_MUTEX(omap_dvfs_lock);
  190 +
  191 +/* Dvfs scale helper function */
  192 +static int _dvfs_scale(struct device *req_dev, struct device *target_dev,
  193 + struct omap_vdd_dvfs_info *tdvfs_info);
  194 +
  195 +/* Few search functions to traverse and find pointers of interest */
  196 +
  197 +/**
  198 + * _dvfs_info_to_dev() - Locate the parent device associated to dvfs_info
  199 + * @dvfs_info: dvfs_info to search for
  200 + *
  201 + * Returns NULL on failure.
  202 + */
  203 +static struct device *_dvfs_info_to_dev(struct omap_vdd_dvfs_info *dvfs_info)
  204 +{
  205 + struct omap_vdd_dev_list *tmp_dev;
  206 + if (IS_ERR_OR_NULL(dvfs_info))
  207 + return NULL;
  208 + if (list_empty(&dvfs_info->dev_list))
  209 + return NULL;
  210 + tmp_dev = list_first_entry(&dvfs_info->dev_list,
  211 + struct omap_vdd_dev_list, node);
  212 + return tmp_dev->dev;
  213 +}
  214 +
  215 +/**
  216 + * _dev_to_dvfs_info() - Locate the dvfs_info for a device
  217 + * @dev: dev to search for
  218 + *
  219 + * Returns NULL on failure.
  220 + */
  221 +static struct omap_vdd_dvfs_info *_dev_to_dvfs_info(struct device *dev)
  222 +{
  223 + struct omap_vdd_dvfs_info *dvfs_info;
  224 + struct omap_vdd_dev_list *temp_dev;
  225 +
  226 + if (IS_ERR_OR_NULL(dev))
  227 + return NULL;
  228 +
  229 + list_for_each_entry(dvfs_info, &omap_dvfs_info_list, node) {
  230 + list_for_each_entry(temp_dev, &dvfs_info->dev_list, node) {
  231 + if (temp_dev->dev == dev)
  232 + return dvfs_info;
  233 + }
  234 + }
  235 +
  236 + return NULL;
  237 +}
  238 +
  239 +/**
  240 + * _voltdm_to_dvfs_info() - Locate a dvfs_info given a voltdm pointer
  241 + * @voltdm: voltdm to search for
  242 + *
  243 + * Returns NULL on failure.
  244 + */
  245 +static
  246 +struct omap_vdd_dvfs_info *_voltdm_to_dvfs_info(struct voltagedomain *voltdm)
  247 +{
  248 + struct omap_vdd_dvfs_info *dvfs_info;
  249 +
  250 + if (IS_ERR_OR_NULL(voltdm))
  251 + return NULL;
  252 +
  253 + list_for_each_entry(dvfs_info, &omap_dvfs_info_list, node) {
  254 + if (dvfs_info->voltdm == voltdm)
  255 + return dvfs_info;
  256 + }
  257 +
  258 + return NULL;
  259 +}
  260 +
  261 +/**
  262 + * _volt_to_opp() - Find OPP corresponding to a given voltage
  263 + * @dev: device pointer associated with the OPP list
  264 + * @volt: voltage to search for in uV
  265 + *
  266 + * Searches for exact match in the OPP list and returns handle to the matching
  267 + * OPP if found, else returns ERR_PTR in case of error and should be handled
  268 + * using IS_ERR. If there are multiple opps with same voltage, it will return
  269 + * the first available entry. Return pointer should be checked against IS_ERR.
  270 + *
  271 + * NOTE: since this uses OPP functions, use under rcu_lock. This function also
  272 + * assumes that the cpufreq table and OPP table are in sync - any modifications
  273 + * to either should be synchronized.
  274 + */
  275 +static struct opp *_volt_to_opp(struct device *dev, unsigned long volt)
  276 +{
  277 + struct opp *opp = ERR_PTR(-ENODEV);
  278 + unsigned long f = 0;
  279 +
  280 + do {
  281 + opp = opp_find_freq_ceil(dev, &f);
  282 + if (IS_ERR(opp))
  283 + break;
  284 + if (opp_get_voltage(opp) >= volt)
  285 + break;
  286 + f++;
  287 + } while (1);
  288 +
  289 + return opp;
  290 +}
  291 +
  292 +/* rest of the helper functions */
  293 +/**
  294 + * _add_vdd_user() - Add a voltage request
  295 + * @dvfs_info: omap_vdd_dvfs_info pointer for the required vdd
  296 + * @dev: device making the request
  297 + * @volt: requested voltage in uV
  298 + *
  299 + * Adds the given device's voltage request into corresponding
  300 + * vdd's omap_vdd_dvfs_info user list (plist). This list is used
  301 + * to find the maximum voltage request for a given vdd.
  302 + *
  303 + * Returns 0 on success.
  304 + */
  305 +static int _add_vdd_user(struct omap_vdd_dvfs_info *dvfs_info,
  306 + struct device *dev, unsigned long volt)
  307 +{
  308 + struct omap_vdd_user_list *user = NULL, *temp_user;
  309 +
  310 + if (!dvfs_info || IS_ERR(dvfs_info)) {
  311 + dev_warn(dev, "%s: VDD specified does not exist!\n", __func__);
  312 + return -EINVAL;
  313 + }
  314 +
  315 + spin_lock(&dvfs_info->user_lock);
  316 + plist_for_each_entry(temp_user, &dvfs_info->vdd_user_list, node) {
  317 + if (temp_user->dev == dev) {
  318 + user = temp_user;
  319 + break;
  320 + }
  321 + }
  322 +
  323 + if (!user) {
  324 + user = kzalloc(sizeof(struct omap_vdd_user_list), GFP_ATOMIC);
  325 + if (!user) {
  326 + dev_err(dev,
  327 + "%s: Unable to creat a new user for vdd_%s\n",
  328 + __func__, dvfs_info->voltdm->name);
  329 + spin_unlock(&dvfs_info->user_lock);
  330 + return -ENOMEM;
  331 + }
  332 + user->dev = dev;
  333 + } else {
  334 + plist_del(&user->node, &dvfs_info->vdd_user_list);
  335 + }
  336 +
  337 + plist_node_init(&user->node, volt);
  338 + plist_add(&user->node, &dvfs_info->vdd_user_list);
  339 +
  340 + spin_unlock(&dvfs_info->user_lock);
  341 + return 0;
  342 +}
  343 +
  344 +/**
  345 + * _remove_vdd_user() - Remove a voltage request
  346 + * @dvfs_info: omap_vdd_dvfs_info pointer for the required vdd
  347 + * @dev: device making the request
  348 + *
  349 + * Removes the given device's voltage request from corresponding
  350 + * vdd's omap_vdd_dvfs_info user list (plist).
  351 + *
  352 + * Returns 0 on success.
  353 + */
  354 +static int _remove_vdd_user(struct omap_vdd_dvfs_info *dvfs_info,
  355 + struct device *dev)
  356 +{
  357 + struct omap_vdd_user_list *user = NULL, *temp_user;
  358 + int ret = 0;
  359 +
  360 + if (!dvfs_info || IS_ERR(dvfs_info)) {
  361 + dev_err(dev, "%s: VDD specified does not exist!\n", __func__);
  362 + return -EINVAL;
  363 + }
  364 +
  365 + spin_lock(&dvfs_info->user_lock);
  366 + plist_for_each_entry(temp_user, &dvfs_info->vdd_user_list, node) {
  367 + if (temp_user->dev == dev) {
  368 + user = temp_user;
  369 + break;
  370 + }
  371 + }
  372 +
  373 + if (user)
  374 + plist_del(&user->node, &dvfs_info->vdd_user_list);
  375 + else {
  376 + dev_err(dev, "%s: Unable to find the user for vdd_%s\n",
  377 + __func__, dvfs_info->voltdm->name);
  378 + ret = -ENOENT;
  379 + }
  380 +
  381 + spin_unlock(&dvfs_info->user_lock);
  382 + kfree(user);
  383 +
  384 + return ret;
  385 +}
  386 +
  387 +/**
  388 + * _add_freq_request() - Add a requested device frequency
  389 + * @dvfs_info: omap_vdd_dvfs_info pointer for the required vdd
  390 + * @req_dev: device making the request
  391 + * @target_dev: target device for which frequency request is being made
  392 + * @freq: target device frequency
  393 + *
  394 + * This adds a requested frequency into target device's frequency list.
  395 + *
  396 + * Returns 0 on success.
  397 + */
  398 +static int _add_freq_request(struct omap_vdd_dvfs_info *dvfs_info,
  399 + struct device *req_dev, struct device *target_dev, unsigned long freq)
  400 +{
  401 + struct omap_dev_user_list *dev_user = NULL, *tmp_user;
  402 + struct omap_vdd_dev_list *temp_dev;
  403 +
  404 + if (!dvfs_info || IS_ERR(dvfs_info)) {
  405 + dev_warn(target_dev, "%s: VDD specified does not exist!\n",
  406 + __func__);
  407 + return -EINVAL;
  408 + }
  409 +
  410 + list_for_each_entry(temp_dev, &dvfs_info->dev_list, node) {
  411 + if (temp_dev->dev == target_dev)
  412 + break;
  413 + }
  414 +
  415 + if (temp_dev->dev != target_dev) {
  416 + dev_warn(target_dev, "%s: target_dev does not exist!\n",
  417 + __func__);
  418 + return -EINVAL;
  419 + }
  420 +
  421 + spin_lock(&temp_dev->user_lock);
  422 + plist_for_each_entry(tmp_user, &temp_dev->freq_user_list, node) {
  423 + if (tmp_user->dev == req_dev) {
  424 + dev_user = tmp_user;
  425 + break;
  426 + }
  427 + }
  428 +
  429 + if (!dev_user) {
  430 + dev_user = kzalloc(sizeof(struct omap_dev_user_list),
  431 + GFP_ATOMIC);
  432 + if (!dev_user) {
  433 + dev_err(target_dev,
  434 + "%s: Unable to creat a new user for vdd_%s\n",
  435 + __func__, dvfs_info->voltdm->name);
  436 + spin_unlock(&temp_dev->user_lock);
  437 + return -ENOMEM;
  438 + }
  439 + dev_user->dev = req_dev;
  440 + } else {
  441 + plist_del(&dev_user->node, &temp_dev->freq_user_list);
  442 + }
  443 +
  444 + plist_node_init(&dev_user->node, freq);
  445 + plist_add(&dev_user->node, &temp_dev->freq_user_list);
  446 + spin_unlock(&temp_dev->user_lock);
  447 + return 0;
  448 +}
  449 +
  450 +/**
  451 + * _remove_freq_request() - Remove the requested device frequency
  452 + *
  453 + * @dvfs_info: omap_vdd_dvfs_info pointer for the required vdd
  454 + * @req_dev: device removing the request
  455 + * @target_dev: target device from which frequency request is being removed
  456 + *
  457 + * This removes a requested frequency from target device's frequency list.
  458 + *
  459 + * Returns 0 on success.
  460 + */
  461 +static int _remove_freq_request(struct omap_vdd_dvfs_info *dvfs_info,
  462 + struct device *req_dev, struct device *target_dev)
  463 +{
  464 + struct omap_dev_user_list *dev_user = NULL, *tmp_user;
  465 + int ret = 0;
  466 + struct omap_vdd_dev_list *temp_dev;
  467 +
  468 + if (!dvfs_info || IS_ERR(dvfs_info)) {
  469 + dev_warn(target_dev, "%s: VDD specified does not exist!\n",
  470 + __func__);
  471 + return -EINVAL;
  472 + }
  473 +
  474 +
  475 + list_for_each_entry(temp_dev, &dvfs_info->dev_list, node) {
  476 + if (temp_dev->dev == target_dev)
  477 + break;
  478 + }
  479 +
  480 + if (temp_dev->dev != target_dev) {
  481 + dev_warn(target_dev, "%s: target_dev does not exist!\n",
  482 + __func__);
  483 + return -EINVAL;
  484 + }
  485 +
  486 + spin_lock(&temp_dev->user_lock);
  487 + plist_for_each_entry(tmp_user, &temp_dev->freq_user_list, node) {
  488 + if (tmp_user->dev == req_dev) {
  489 + dev_user = tmp_user;
  490 + break;
  491 + }
  492 + }
  493 +
  494 + if (dev_user) {
  495 + plist_del(&dev_user->node, &temp_dev->freq_user_list);
  496 + } else {
  497 + dev_err(target_dev,
  498 + "%s: Unable to remove the user for vdd_%s\n",
  499 + __func__, dvfs_info->voltdm->name);
  500 + ret = -EINVAL;
  501 + }
  502 +
  503 + spin_unlock(&temp_dev->user_lock);
  504 + kfree(dev_user);
  505 +
  506 + return ret;
  507 +}
  508 +
  509 +/**
  510 + * _dep_scan_table() - Scan a dependency table and mark for scaling
  511 + * @dev: device requesting the dependency scan (req_dev)
  512 + * @dep_info: dependency information (contains the table)
  513 + * @main_volt: voltage dependency to search for
  514 + *
  515 + * This runs down the table provided to find the match for main_volt
  516 + * provided and sets up a scale request for the dependent domain
  517 + * for the dependent voltage.
  518 + *
  519 + * Returns 0 if all went well.
  520 + */
  521 +static int _dep_scan_table(struct device *dev,
  522 + struct omap_vdd_dep_info *dep_info, unsigned long main_volt)
  523 +{
  524 + struct omap_vdd_dep_volt *dep_table = dep_info->dep_table;
  525 + int i;
  526 + unsigned long dep_volt = 0;
  527 +
  528 + if (!dep_table) {
  529 + dev_err(dev, "%s: deptable not present for vdd%s\n",
  530 + __func__, dep_info->name);
  531 + return -EINVAL;
  532 + }
  533 +
  534 + /* Now scan through the the dep table for a match */
  535 + for (i = 0; i < dep_info->nr_dep_entries; i++) {
  536 + if (dep_table[i].main_vdd_volt == main_volt) {
  537 + dep_volt = dep_table[i].dep_vdd_volt;
  538 + break;
  539 + }
  540 + }
  541 + if (!dep_volt) {
  542 + dev_warn(dev, "%s: %ld volt map missing in vdd_%s\n",
  543 + __func__, main_volt, dep_info->name);
  544 + return -EINVAL;
  545 + }
  546 +
  547 + /* populate voltdm if it is not present */
  548 + if (!dep_info->_dep_voltdm) {
  549 + dep_info->_dep_voltdm = voltdm_lookup(dep_info->name);
  550 + if (!dep_info->_dep_voltdm) {
  551 + dev_warn(dev, "%s: unable to get vdm%s\n",
  552 + __func__, dep_info->name);
  553 + return -ENODEV;
  554 + }
  555 + }
  556 +
  557 + /* See if dep_volt is possible for the vdd*/
  558 + i = _add_vdd_user(_voltdm_to_dvfs_info(dep_info->_dep_voltdm),
  559 + dev, dep_volt);
  560 + if (i)
  561 + dev_err(dev, "%s: Failed to add dep to domain %s volt=%ld\n",
  562 + __func__, dep_info->name, dep_volt);
  563 + return i;
  564 +}
  565 +
  566 +/**
  567 + * _dep_scan_domains() - Scan dependency domains for a device
  568 + * @dev: device requesting the scan
  569 + * @vdd: vdd_info corresponding to the device
  570 + * @main_volt: voltage to scan for
  571 + *
  572 + * Since each domain *may* have multiple dependent domains, we scan
  573 + * through each of the dependent domains and invoke _dep_scan_table to
  574 + * scan each table for dependent domain for dependency scaling.
  575 + *
  576 + * This assumes that the dependent domain information is NULL entry terminated.
  577 + * Returns 0 if all went well.
  578 + */
  579 +static int _dep_scan_domains(struct device *dev,
  580 + struct omap_vdd_info *vdd, unsigned long main_volt)
  581 +{
  582 + struct omap_vdd_dep_info *dep_info = vdd->dep_vdd_info;
  583 + int ret = 0, r;
  584 +
  585 + if (!dep_info) {
  586 + dev_dbg(dev, "%s: No dependent VDD\n", __func__);
  587 + return 0;
  588 + }
  589 +
  590 + /* First scan through the mydomain->dep_domain list */
  591 + while (dep_info->nr_dep_entries) {
  592 + r = _dep_scan_table(dev, dep_info, main_volt);
  593 + /* Store last failed value */
  594 + ret = (r) ? r : ret;
  595 + dep_info++;
  596 + }
  597 +
  598 + return ret;
  599 +}
  600 +
  601 +/**
  602 + * _dep_scale_domains() - Cause a scale of all dependent domains
  603 + * @req_dev: device requesting the scale
  604 + * @req_vdd: vdd_info corresponding to the requesting device.
  605 + *
  606 + * This walks through every dependent domain and triggers a scale
  607 + * It is assumed that the corresponding scale handling for the
  608 + * domain translates this to freq and voltage scale operations as
  609 + * needed.
  610 + *
  611 + * Note: This is uses _dvfs_scale and one should be careful not to
  612 + * create a circular depedency (e.g. vdd_mpu->vdd_core->vdd->mpu)
  613 + * which can create deadlocks. No protection is provided to prevent
  614 + * this condition and a tree organization is assumed.
  615 + *
  616 + * Returns 0 if all went fine.
  617 + */
  618 +static int _dep_scale_domains(struct device *req_dev,
  619 + struct omap_vdd_info *req_vdd)
  620 +{
  621 + struct omap_vdd_dep_info *dep_info = req_vdd->dep_vdd_info;
  622 + int ret = 0, r;
  623 +
  624 + if (!dep_info) {
  625 + dev_dbg(req_dev, "%s: No dependent VDD\n", __func__);
  626 + return 0;
  627 + }
  628 +
  629 + /* First scan through the mydomain->dep_domain list */
  630 + while (dep_info->nr_dep_entries) {
  631 + struct voltagedomain *tvoltdm = dep_info->_dep_voltdm;
  632 +
  633 + r = 0;
  634 + /* Scale it only if I have a voltdm mapped up for the dep */
  635 + if (tvoltdm) {
  636 + struct omap_vdd_dvfs_info *tdvfs_info;
  637 + struct device *target_dev;
  638 + tdvfs_info = _voltdm_to_dvfs_info(tvoltdm);
  639 + if (!tdvfs_info) {
  640 + dev_warn(req_dev, "%s: no dvfs_info\n",
  641 + __func__);
  642 + goto next;
  643 + }
  644 + target_dev = _dvfs_info_to_dev(tdvfs_info);
  645 + if (!target_dev) {
  646 + dev_warn(req_dev, "%s: no target_dev\n",
  647 + __func__);
  648 + goto next;
  649 + }
  650 + r = _dvfs_scale(req_dev, target_dev, tdvfs_info);
  651 +next:
  652 + if (r)
  653 + dev_err(req_dev, "%s: dvfs_scale to %s =%d\n",
  654 + __func__, dev_name(target_dev), r);
  655 + }
  656 + /* Store last failed value */
  657 + ret = (r) ? r : ret;
  658 + dep_info++;
  659 + }
  660 +
  661 + return ret;
  662 +}
  663 +
  664 +/**
  665 + * _dvfs_scale() : Scale the devices associated with a voltage domain
  666 + * @req_dev: Device requesting the scale
  667 + * @target_dev: Device requesting to be scaled
  668 + * @tdvfs_info: omap_vdd_dvfs_info pointer for the target domain
  669 + *
  670 + * This runs through the list of devices associated with the
  671 + * voltage domain and scales the device rates to the one requested
  672 + * by the user or those corresponding to the new voltage of the
  673 + * voltage domain. Target voltage is the highest voltage in the vdd_user_list.
  674 + *
  675 + * Returns 0 on success else the error value.
  676 + */
  677 +static int _dvfs_scale(struct device *req_dev, struct device *target_dev,
  678 + struct omap_vdd_dvfs_info *tdvfs_info)
  679 +{
  680 + unsigned long curr_volt, new_volt;
  681 + int volt_scale_dir = DVFS_VOLT_SCALE_DOWN;
  682 + struct omap_vdd_dev_list *temp_dev;
  683 + struct plist_node *node;
  684 + int ret = 0;
  685 + struct voltagedomain *voltdm;
  686 + struct omap_vdd_info *vdd;
  687 +
  688 + voltdm = tdvfs_info->voltdm;
  689 + if (IS_ERR_OR_NULL(voltdm)) {
  690 + dev_err(target_dev, "%s: bad voltdm\n", __func__);
  691 + return -EINVAL;
  692 + }
  693 + vdd = voltdm->vdd;
  694 +
  695 + /* Find the highest voltage being requested */
  696 + node = plist_last(&tdvfs_info->vdd_user_list);
  697 + new_volt = node->prio;
  698 +
  699 + curr_volt = voltdm_get_voltage(voltdm);
  700 +
  701 + /* Disable smartreflex module across voltage and frequency scaling */
  702 + omap_sr_disable(voltdm);
  703 +
  704 + if (curr_volt == new_volt) {
  705 + volt_scale_dir = DVFS_VOLT_SCALE_NONE;
  706 + } else if (curr_volt < new_volt) {
  707 + ret = voltdm_scale(voltdm, new_volt);
  708 + if (ret) {
  709 + dev_err(target_dev,
  710 + "%s: Unable to scale the %s to %ld volt\n",
  711 + __func__, voltdm->name, new_volt);
  712 + goto out;
  713 + }
  714 + volt_scale_dir = DVFS_VOLT_SCALE_UP;
  715 + }
  716 +
  717 + /* if we fail scale for dependent domains, go back to prev state */
  718 + ret = _dep_scan_domains(target_dev, vdd, new_volt);
  719 + if (ret) {
  720 + dev_err(target_dev,
  721 + "%s: Error in scan domains for vdd_%s\n",
  722 + __func__, voltdm->name);
  723 + goto fail;
  724 + }
  725 +
  726 + /* unless we are moving down, scale dependents before we shift freq */
  727 + if (!(DVFS_VOLT_SCALE_DOWN == volt_scale_dir)) {
  728 + ret = _dep_scale_domains(target_dev, vdd);
  729 + if (ret) {
  730 + dev_err(target_dev,
  731 + "%s: Error(%d)scale dependent with %ld volt\n",
  732 + __func__, ret, new_volt);
  733 + goto fail;
  734 + }
  735 + }
  736 +
  737 + /* Move all devices in list to the required frequencies */
  738 + list_for_each_entry(temp_dev, &tdvfs_info->dev_list, node) {
  739 + struct device *dev;
  740 + struct opp *opp;
  741 + unsigned long freq = 0;
  742 + int r;
  743 +
  744 + dev = temp_dev->dev;
  745 + if (!plist_head_empty(&temp_dev->freq_user_list)) {
  746 + node = plist_last(&temp_dev->freq_user_list);
  747 + freq = node->prio;
  748 + } else {
  749 + /* dep domain? we'd probably have a voltage request */
  750 + rcu_read_lock();
  751 + opp = _volt_to_opp(dev, new_volt);
  752 + if (!IS_ERR(opp))
  753 + freq = opp_get_freq(opp);
  754 + rcu_read_unlock();
  755 + if (!freq)
  756 + continue;
  757 + }
  758 +
  759 + if (freq == clk_get_rate(temp_dev->clk)) {
  760 + dev_dbg(dev, "%s: Already at the requested"
  761 + "rate %ld\n", __func__, freq);
  762 + continue;
  763 + }
  764 +
  765 + r = clk_set_rate(temp_dev->clk, freq);
  766 + if (r < 0) {
  767 + dev_err(dev, "%s: clk set rate frq=%ld failed(%d)\n",
  768 + __func__, freq, r);
  769 + ret = r;
  770 + }
  771 + }
  772 +
  773 + if (ret)
  774 + goto fail;
  775 +
  776 + if (DVFS_VOLT_SCALE_DOWN == volt_scale_dir) {
  777 + voltdm_scale(voltdm, new_volt);
  778 + _dep_scale_domains(target_dev, vdd);
  779 + }
  780 +
  781 + /* All clear.. go out gracefully */
  782 + goto out;
  783 +
  784 +fail:
  785 + pr_warning("%s: domain%s: No clean recovery available! could be bad!\n",
  786 + __func__, voltdm->name);
  787 +out:
  788 + /* Re-enable Smartreflex module */
  789 + omap_sr_enable(voltdm);
  790 +
  791 + return ret;
  792 +}
  793 +
  794 +/* Public functions */
  795 +
  796 +/**
  797 + * omap_device_scale() - Set a new rate at which the device is to operate
  798 + * @req_dev: pointer to the device requesting the scaling.
  799 + * @target_dev: pointer to the device that is to be scaled
  800 + * @rate: the rnew rate for the device.
  801 + *
  802 + * This API gets the device opp table associated with this device and
  803 + * tries putting the device to the requested rate and the voltage domain
  804 + * associated with the device to the voltage corresponding to the
  805 + * requested rate. Since multiple devices can be assocciated with a
  806 + * voltage domain this API finds out the possible voltage the
  807 + * voltage domain can enter and then decides on the final device
  808 + * rate.
  809 + *
  810 + * Return 0 on success else the error value
  811 + */
  812 +int omap_device_scale(struct device *req_dev, struct device *target_dev,
  813 + unsigned long rate)
  814 +{
  815 + struct opp *opp;
  816 + unsigned long volt, freq = rate;
  817 + struct omap_vdd_dvfs_info *tdvfs_info;
  818 + struct platform_device *pdev;
  819 + int ret = 0;
  820 +
  821 + pdev = container_of(target_dev, struct platform_device, dev);
  822 + if (IS_ERR_OR_NULL(pdev)) {
  823 + pr_err("%s: pdev is null!\n", __func__);
  824 + return -EINVAL;
  825 + }
  826 +
  827 + /* Lock me to ensure cross domain scaling is secure */
  828 + mutex_lock(&omap_dvfs_lock);
  829 +
  830 + rcu_read_lock();
  831 + opp = opp_find_freq_ceil(target_dev, &freq);
  832 + if (IS_ERR(opp)) {
  833 + rcu_read_unlock();
  834 + dev_err(target_dev, "%s: Unable to find OPP for freq%ld\n",
  835 + __func__, rate);
  836 + ret = -ENODEV;
  837 + goto out;
  838 + }
  839 + volt = opp_get_voltage(opp);
  840 + rcu_read_unlock();
  841 +
  842 + tdvfs_info = _dev_to_dvfs_info(target_dev);
  843 + if (IS_ERR_OR_NULL(tdvfs_info)) {
  844 + dev_err(target_dev, "%s: (req=%s) no vdd![f=%ld, v=%ld]\n",
  845 + __func__, dev_name(req_dev), freq, volt);
  846 + ret = -ENODEV;
  847 + goto out;
  848 + }
  849 +
  850 + ret = _add_freq_request(tdvfs_info, req_dev, target_dev, freq);
  851 + if (ret) {
  852 + dev_err(target_dev, "%s: freqadd(%s) failed %d[f=%ld, v=%ld]\n",
  853 + __func__, dev_name(req_dev), ret, freq, volt);
  854 + goto out;
  855 + }
  856 +
  857 + ret = _add_vdd_user(tdvfs_info, req_dev, volt);
  858 + if (ret) {
  859 + dev_err(target_dev, "%s: vddadd(%s) failed %d[f=%ld, v=%ld]\n",
  860 + __func__, dev_name(req_dev), ret, freq, volt);
  861 + _remove_freq_request(tdvfs_info, req_dev,
  862 + target_dev);
  863 + goto out;
  864 + }
  865 +
  866 + /* Do the actual scaling */
  867 + ret = _dvfs_scale(req_dev, target_dev, tdvfs_info);
  868 + if (ret) {
  869 + dev_err(target_dev, "%s: scale by %s failed %d[f=%ld, v=%ld]\n",
  870 + __func__, dev_name(req_dev), ret, freq, volt);
  871 + _remove_freq_request(tdvfs_info, req_dev,
  872 + target_dev);
  873 + _remove_vdd_user(tdvfs_info, target_dev);
  874 + /* Fall through */
  875 + }
  876 + /* Fall through */
  877 +out:
  878 + mutex_unlock(&omap_dvfs_lock);
  879 + return ret;
  880 +}
  881 +EXPORT_SYMBOL(omap_device_scale);
  882 +
  883 +/**
  884 + * omap_dvfs_register_device - Add a parent device into dvfs managed list
  885 + * @dev: Device to be added
  886 + * @voltdm_name: Name of the voltage domain for the device
  887 + * @clk_name: Name of the clock for the device
  888 + *
  889 + * This function adds a given device into user_list of corresponding
  890 + * vdd's omap_vdd_dvfs_info strucure. This list is traversed to scale
  891 + * frequencies of all the devices on a given vdd.
  892 + *
  893 + * Returns 0 on success.
  894 + */
  895 +int __init omap_dvfs_register_device(struct device *dev, char *voltdm_name,
  896 + char *clk_name)
  897 +{
  898 + struct omap_vdd_dev_list *temp_dev;
  899 + struct omap_vdd_dvfs_info *dvfs_info;
  900 + struct clk *clk = NULL;
  901 + int ret = 0;
  902 +
  903 + if (!voltdm_name) {
  904 + dev_err(dev, "%s: Bad voltdm name!\n", __func__);
  905 + return -EINVAL;
  906 + }
  907 + if (!clk_name) {
  908 + dev_err(dev, "%s: Bad clk name!\n", __func__);
  909 + return -EINVAL;
  910 + }
  911 +
  912 + /* Lock me to secure structure changes */
  913 + mutex_lock(&omap_dvfs_lock);
  914 +
  915 + dvfs_info = _dev_to_dvfs_info(dev);
  916 + if (!dvfs_info) {
  917 + dvfs_info = kzalloc(sizeof(struct omap_vdd_dvfs_info),
  918 + GFP_KERNEL);
  919 + if (!dvfs_info) {
  920 + dev_warn(dev, "%s: unable to alloc memory!\n",
  921 + __func__);
  922 + ret = -ENOMEM;
  923 + goto out;
  924 + }
  925 + dvfs_info->voltdm = voltdm_lookup(voltdm_name);
  926 + if (!dvfs_info->voltdm) {
  927 + dev_warn(dev, "%s: unable to find voltdm %s!\n",
  928 + __func__, voltdm_name);
  929 + kfree(dvfs_info);
  930 + ret = -EINVAL;
  931 + goto out;
  932 + }
  933 +
  934 + /* Init the plist */
  935 + spin_lock_init(&dvfs_info->user_lock);
  936 + plist_head_init(&dvfs_info->vdd_user_list);
  937 +
  938 + /* Init the device list */
  939 + INIT_LIST_HEAD(&dvfs_info->dev_list);
  940 +
  941 + list_add(&dvfs_info->node, &omap_dvfs_info_list);
  942 + }
  943 +
  944 + /* If device already added, we dont need to do more.. */
  945 + list_for_each_entry(temp_dev, &dvfs_info->dev_list, node) {
  946 + if (temp_dev->dev == dev)
  947 + goto out;
  948 + }
  949 +
  950 + temp_dev = kzalloc(sizeof(struct omap_vdd_dev_list), GFP_KERNEL);
  951 + if (!temp_dev) {
  952 + dev_err(dev, "%s: Unable to creat a new device for vdd_%s\n",
  953 + __func__, dvfs_info->voltdm->name);
  954 + ret = -ENOMEM;
  955 + goto out;
  956 + }
  957 +
  958 + clk = clk_get(dev, clk_name);
  959 + if (IS_ERR_OR_NULL(clk)) {
  960 + dev_warn(dev, "%s: Bad clk pointer!\n", __func__);
  961 + kfree(temp_dev);
  962 + ret = -EINVAL;
  963 + goto out;
  964 + }
  965 +
  966 + /* Initialize priority ordered list */
  967 + spin_lock_init(&temp_dev->user_lock);
  968 + plist_head_init(&temp_dev->freq_user_list);
  969 +
  970 + temp_dev->dev = dev;
  971 + temp_dev->clk = clk;
  972 + list_add_tail(&temp_dev->node, &dvfs_info->dev_list);
  973 +
  974 + /* Fall through */
  975 +out:
  976 + mutex_unlock(&omap_dvfs_lock);
  977 + return ret;
  978 +}
arch/arm/mach-omap2/dvfs.h
  1 +/*
  2 + * OMAP3/OMAP4 DVFS Management Routines
  3 + *
  4 + * Author: Vishwanath BS <vishwanath.bs@ti.com>
  5 + *
  6 + * Copyright (C) 2011 Texas Instruments, Inc.
  7 + * Vishwanath BS <vishwanath.bs@ti.com>
  8 + *
  9 + * This program is free software; you can redistribute it and/or modify
  10 + * it under the terms of the GNU General Public License version 2 as
  11 + * published by the Free Software Foundation.
  12 + */
  13 +
  14 +#ifndef __ARCH_ARM_MACH_OMAP2_DVFS_H
  15 +#define __ARCH_ARM_MACH_OMAP2_DVFS_H
  16 +#include <plat/omap_hwmod.h>
  17 +#include "voltage.h"
  18 +
  19 +#ifdef CONFIG_PM
  20 +int omap_dvfs_register_device(struct device *dev, char *voltdm_name,
  21 + char *clk_name);
  22 +int omap_device_scale(struct device *req_dev, struct device *target_dev,
  23 + unsigned long rate);
  24 +#else
  25 +static inline int omap_dvfs_register_device(struct omap_hwmod *oh,
  26 + struct device *dev)
  27 +static inline int omap_dvfs_register_device(struct device *dev,
  28 + char *voltdm_name, char *clk_name)
  29 +{
  30 + return -EINVAL;
  31 +}
  32 +static inline int omap_device_scale(struct device *req_dev,
  33 + struct device *target_dev, unsigned long rate)
  34 +{
  35 + return -EINVAL;
  36 +}
  37 +#endif
  38 +#endif