aboutsummaryrefslogtreecommitdiffstats
path: root/arch/arm/mach-omap2/dvfs.c
diff options
context:
space:
mode:
Diffstat (limited to 'arch/arm/mach-omap2/dvfs.c')
-rw-r--r--arch/arm/mach-omap2/dvfs.c1314
1 files changed, 1314 insertions, 0 deletions
diff --git a/arch/arm/mach-omap2/dvfs.c b/arch/arm/mach-omap2/dvfs.c
new file mode 100644
index 0000000..e00032b
--- /dev/null
+++ b/arch/arm/mach-omap2/dvfs.c
@@ -0,0 +1,1314 @@
+/*
+ * OMAP3/OMAP4 DVFS Management Routines
+ *
+ * Author: Vishwanath BS <vishwanath.bs@ti.com>
+ *
+ * Copyright (C) 2011 Texas Instruments, Inc.
+ * Vishwanath BS <vishwanath.bs@ti.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/err.h>
+#include <linux/spinlock.h>
+#include <linux/plist.h>
+#include <linux/slab.h>
+#include <linux/opp.h>
+#include <linux/clk.h>
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
+#include <plat/common.h>
+#include <plat/omap_device.h>
+#include <plat/omap_hwmod.h>
+#include <plat/clock.h>
+#include "dvfs.h"
+#include "smartreflex.h"
+#include "powerdomain.h"
+#include "pm.h"
+
+/**
+ * DOC: Introduction
+ * =================
+ * DVFS is a technique that uses the optimal operating frequency and voltage to
+ * allow a task to be performed in the required amount of time.
+ * OMAP processors have voltage domains whose voltage can be scaled to
+ * various levels depending on which the operating frequencies of certain
+ * devices belonging to the domain will also need to be scaled. This voltage
+ * frequency tuple is known as Operating Performance Point (OPP). A device
+ * can have multiple OPP's. Also a voltage domain could be shared between
+ * multiple devices. Also there could be dependencies between various
+ * voltage domains for maintaining system performance like VDD<X>
+ * should be at voltage v1 when VDD<Y> is at voltage v2.
+ *
+ * The design of this framework takes into account all the above mentioned
+ * points. To summarize the basic design of DVFS framework:-
+ *
+ * 1. Have device opp tables for each device whose operating frequency can be
+ * scaled. This is easy now due to the existance of hwmod layer which
+ * allow storing of device specific info. The device opp tables contain
+ * the opp pairs (frequency voltage tuples), the voltage domain pointer
+ * to which the device belongs to, the device specific set_rate and
+ * get_rate API's which will do the actual scaling of the device frequency
+ * and retrieve the current device frequency.
+ * 2. Introduce use counting on a per VDD basis. This is to take care multiple
+ * requests to scale a VDD. The VDD will be scaled to the maximum of the
+ * voltages requested.
+ * 3. Keep track of all scalable devices belonging to a particular voltage
+ * domain the voltage layer.
+ * 4. Keep track of frequency requests for each of the device. This will enable
+ * to scale individual devices to different frequency (even w/o scaling
+ * voltage aka frequency throttling)
+ * 5. Generic dvfs API that can be called by anybody to scale a device opp.
+ * This API takes the device pointer and frequency to which the device
+ * needs to be scaled to. This API then internally finds out the voltage
+ * domain to which the device belongs to and the voltage to which the voltage
+ * domain needs to be put to for the device to be scaled to the new frequency
+ * from the device opp table. Then this API will add requested frequency into
+ * the corresponding target device frequency list and add voltage request to
+ * the corresponding vdd. Subsequently it calls voltage scale function which
+ * will find out the highest requested voltage for the given vdd and scales
+ * the voltage to the required one and also adds corresponding frequency
+ * request for that voltage. It also runs through the list of all
+ * scalable devices belonging to this voltage domain and scale them to the
+ * appropriate frequencies using the set_rate pointer in the device opp
+ * tables.
+ * 6. Handle inter VDD dependecies. This will take care of scaling domain's voltage
+ * and frequency together.
+ *
+ *
+ * DOC: The Core DVFS data structure:
+ * ==================================
+ * Structure Name Example Tree
+ * ---------
+ * /|\ +-------------------+ +-------------------+
+ * | |User2 (dev2, freq2)+---\ |User4 (dev4, freq4)+---\
+ * | +-------------------+ | +-------------------+ |
+ * (struct omap_dev_user_list) | |
+ * | +-------------------+ | +-------------------+ |
+ * | |User1 (dev1, freq1)+---| |User3 (dev3, freq3)+---|
+ * \|/ +-------------------+ | +-------------------+ |
+ * --------- | |
+ * /|\ +------------+------+ +---------------+--+
+ * | | DEV1 (dev, | | DEV2 (dev) |
+ * (struct omap_vdd_dev_list)|omap_dev_user_list)| |omap_dev_user_list|
+ * | +------------+------+ +--+---------------+
+ * \|/ /|\ /-----+-------------+------> others..
+ * --------- Frequency |
+ * /|\ +--+------------------+
+ * | | VDD_n |
+ * | | (omap_vdd_dev_list, |
+ * (struct omap_vdd_dvfs_info)** | omap_vdd_user_list) |
+ * | +--+------------------+
+ * | | (ROOT NODE: omap_dvfs_info_list)
+ * \|/ |
+ * --------- Voltage \---+-------------+----------> others..
+ * /|\ \|/ +-------+----+ +-----+--------+
+ * | | vdd_user2 | | vdd_user3 |
+ * (struct omap_vdd_user_list) | (dev, volt)| | (dev, volt) |
+ * \|/ +------------+ +--------------+
+ * ---------
+ * Key: ** -> Root of the tree.
+ * NOTE: we use the priority to store the voltage/frequency
+ *
+ * For voltage dependency description, see: struct dependency:
+ * voltagedomain -> (description of the voltagedomain)
+ * omap_vdd_info -> (vdd information)
+ * omap_vdd_dep_info[]-> (stores array of depedency info)
+ * omap_vdd_dep_volt[] -> (stores array of maps)
+ * (main_volt -> dep_volt) (a singular map)
+ */
+
+/* Macros to give idea about scaling directions */
+#define DVFS_VOLT_SCALE_DOWN 0
+#define DVFS_VOLT_SCALE_NONE 1
+#define DVFS_VOLT_SCALE_UP 2
+
+/**
+ * struct omap_dev_user_list - Structure maitain userlist per devide
+ * @dev: The device requesting for a particular frequency
+ * @node: The list head entry
+ *
+ * Using this structure, user list (requesting dev * and frequency) for
+ * each device is maintained. This is how we can have different devices
+ * at different frequencies (to support frequency locking and throttling).
+ * Even if one of the devices in a given vdd has locked it's frequency,
+ * other's can still scale their frequency using this list.
+ * If no one has placed a frequency request for a device, then device is
+ * set to the frequency from it's opp table.
+ */
+struct omap_dev_user_list {
+ struct device *dev;
+ struct plist_node node;
+};
+
+/**
+ * struct omap_vdd_dev_list - Device list per vdd
+ * @dev: The device belonging to a particular vdd
+ * @node: The list head entry
+ * @freq_user_list: The list of users for vdd device
+ * @clk: frequency control clock for this dev
+ * @user_lock: The lock for plist manipulation
+ */
+struct omap_vdd_dev_list {
+ struct device *dev;
+ struct list_head node;
+ struct plist_head freq_user_list;
+ struct clk *clk;
+ spinlock_t user_lock; /* spinlock for plist */
+};
+
+/**
+ * struct omap_vdd_user_list - The per vdd user list
+ * @dev: The device asking for the vdd to be set at a particular
+ * voltage
+ * @node: The list head entry
+ */
+struct omap_vdd_user_list {
+ struct device *dev;
+ struct plist_node node;
+};
+
+/**
+ * struct omap_vdd_dvfs_info - The per vdd dvfs info
+ * @node: list node for vdd_dvfs_info list
+ * @user_lock: spinlock for plist operations
+ * @vdd_user_list: The vdd user list
+ * @voltdm: Voltage domains for which dvfs info stored
+ * @dev_list: Device list maintained per domain
+ *
+ * This is a fundamental structure used to store all the required
+ * DVFS related information for a vdd.
+ */
+struct omap_vdd_dvfs_info {
+ struct list_head node;
+
+ spinlock_t user_lock; /* spin lock */
+ struct plist_head vdd_user_list;
+ struct voltagedomain *voltdm;
+ struct list_head dev_list;
+};
+
+static LIST_HEAD(omap_dvfs_info_list);
+DEFINE_MUTEX(omap_dvfs_lock);
+
+/* Dvfs scale helper function */
+static int _dvfs_scale(struct device *req_dev, struct device *target_dev,
+ struct omap_vdd_dvfs_info *tdvfs_info);
+
+/* Few search functions to traverse and find pointers of interest */
+
+/**
+ * _dvfs_info_to_dev() - Locate the parent device associated to dvfs_info
+ * @dvfs_info: dvfs_info to search for
+ *
+ * Returns NULL on failure.
+ */
+static struct device *_dvfs_info_to_dev(struct omap_vdd_dvfs_info *dvfs_info)
+{
+ struct omap_vdd_dev_list *tmp_dev;
+ if (IS_ERR_OR_NULL(dvfs_info))
+ return NULL;
+ if (list_empty(&dvfs_info->dev_list))
+ return NULL;
+ tmp_dev = list_first_entry(&dvfs_info->dev_list,
+ struct omap_vdd_dev_list, node);
+ return tmp_dev->dev;
+}
+
+/**
+ * _dev_to_dvfs_info() - Locate the dvfs_info for a device
+ * @dev: dev to search for
+ *
+ * Returns NULL on failure.
+ */
+static struct omap_vdd_dvfs_info *_dev_to_dvfs_info(struct device *dev)
+{
+ struct omap_vdd_dvfs_info *dvfs_info;
+ struct omap_vdd_dev_list *temp_dev;
+
+ if (IS_ERR_OR_NULL(dev))
+ return NULL;
+
+ list_for_each_entry(dvfs_info, &omap_dvfs_info_list, node) {
+ list_for_each_entry(temp_dev, &dvfs_info->dev_list, node) {
+ if (temp_dev->dev == dev)
+ return dvfs_info;
+ }
+ }
+
+ return NULL;
+}
+
+/**
+ * _voltdm_to_dvfs_info() - Locate a dvfs_info given a voltdm pointer
+ * @voltdm: voltdm to search for
+ *
+ * Returns NULL on failure.
+ */
+static
+struct omap_vdd_dvfs_info *_voltdm_to_dvfs_info(struct voltagedomain *voltdm)
+{
+ struct omap_vdd_dvfs_info *dvfs_info;
+
+ if (IS_ERR_OR_NULL(voltdm))
+ return NULL;
+
+ list_for_each_entry(dvfs_info, &omap_dvfs_info_list, node) {
+ if (dvfs_info->voltdm == voltdm)
+ return dvfs_info;
+ }
+
+ return NULL;
+}
+
+/**
+ * _volt_to_opp() - Find OPP corresponding to a given voltage
+ * @dev: device pointer associated with the OPP list
+ * @volt: voltage to search for in uV
+ *
+ * Searches for exact match in the OPP list and returns handle to the matching
+ * OPP if found, else return the max available OPP.
+ * If there are multiple opps with same voltage, it will return
+ * the first available entry. Return pointer should be checked against IS_ERR.
+ *
+ * NOTE: since this uses OPP functions, use under rcu_lock. This function also
+ * assumes that the cpufreq table and OPP table are in sync - any modifications
+ * to either should be synchronized.
+ */
+static struct opp *_volt_to_opp(struct device *dev, unsigned long volt)
+{
+ struct opp *opp = ERR_PTR(-ENODEV);
+ unsigned long f = 0;
+
+ do {
+ opp = opp_find_freq_ceil(dev, &f);
+ if (IS_ERR(opp)) {
+ /*
+ * if there is no OPP for corresponding volt
+ * then return max available instead
+ */
+ opp = opp_find_freq_floor(dev, &f);
+ break;
+ }
+ if (opp_get_voltage(opp) >= volt)
+ break;
+ f++;
+ } while (1);
+
+ return opp;
+}
+
+/* rest of the helper functions */
+/**
+ * _add_vdd_user() - Add a voltage request
+ * @dvfs_info: omap_vdd_dvfs_info pointer for the required vdd
+ * @dev: device making the request
+ * @volt: requested voltage in uV
+ *
+ * Adds the given device's voltage request into corresponding
+ * vdd's omap_vdd_dvfs_info user list (plist). This list is used
+ * to find the maximum voltage request for a given vdd.
+ *
+ * Returns 0 on success.
+ */
+static int _add_vdd_user(struct omap_vdd_dvfs_info *dvfs_info,
+ struct device *dev, unsigned long volt)
+{
+ struct omap_vdd_user_list *user = NULL, *temp_user;
+
+ if (!dvfs_info || IS_ERR(dvfs_info)) {
+ dev_warn(dev, "%s: VDD specified does not exist!\n", __func__);
+ return -EINVAL;
+ }
+
+ spin_lock(&dvfs_info->user_lock);
+ plist_for_each_entry(temp_user, &dvfs_info->vdd_user_list, node) {
+ if (temp_user->dev == dev) {
+ user = temp_user;
+ break;
+ }
+ }
+
+ if (!user) {
+ user = kzalloc(sizeof(struct omap_vdd_user_list), GFP_ATOMIC);
+ if (!user) {
+ dev_err(dev,
+ "%s: Unable to creat a new user for vdd_%s\n",
+ __func__, dvfs_info->voltdm->name);
+ spin_unlock(&dvfs_info->user_lock);
+ return -ENOMEM;
+ }
+ user->dev = dev;
+ } else {
+ plist_del(&user->node, &dvfs_info->vdd_user_list);
+ }
+
+ plist_node_init(&user->node, volt);
+ plist_add(&user->node, &dvfs_info->vdd_user_list);
+
+ spin_unlock(&dvfs_info->user_lock);
+ return 0;
+}
+
+/**
+ * _remove_vdd_user() - Remove a voltage request
+ * @dvfs_info: omap_vdd_dvfs_info pointer for the required vdd
+ * @dev: device making the request
+ *
+ * Removes the given device's voltage request from corresponding
+ * vdd's omap_vdd_dvfs_info user list (plist).
+ *
+ * Returns 0 on success.
+ */
+static int _remove_vdd_user(struct omap_vdd_dvfs_info *dvfs_info,
+ struct device *dev)
+{
+ struct omap_vdd_user_list *user = NULL, *temp_user;
+ int ret = 0;
+
+ if (!dvfs_info || IS_ERR(dvfs_info)) {
+ dev_err(dev, "%s: VDD specified does not exist!\n", __func__);
+ return -EINVAL;
+ }
+
+ spin_lock(&dvfs_info->user_lock);
+ plist_for_each_entry(temp_user, &dvfs_info->vdd_user_list, node) {
+ if (temp_user->dev == dev) {
+ user = temp_user;
+ break;
+ }
+ }
+
+ if (user)
+ plist_del(&user->node, &dvfs_info->vdd_user_list);
+ else {
+ dev_err(dev, "%s: Unable to find the user for vdd_%s\n",
+ __func__, dvfs_info->voltdm->name);
+ ret = -ENOENT;
+ }
+
+ spin_unlock(&dvfs_info->user_lock);
+ kfree(user);
+
+ return ret;
+}
+
+/**
+ * _add_freq_request() - Add a requested device frequency
+ * @dvfs_info: omap_vdd_dvfs_info pointer for the required vdd
+ * @req_dev: device making the request
+ * @target_dev: target device for which frequency request is being made
+ * @freq: target device frequency
+ *
+ * This adds a requested frequency into target device's frequency list.
+ *
+ * Returns 0 on success.
+ */
+static int _add_freq_request(struct omap_vdd_dvfs_info *dvfs_info,
+ struct device *req_dev, struct device *target_dev, unsigned long freq)
+{
+ struct omap_dev_user_list *dev_user = NULL, *tmp_user;
+ struct omap_vdd_dev_list *temp_dev;
+
+ if (!dvfs_info || IS_ERR(dvfs_info)) {
+ dev_warn(target_dev, "%s: VDD specified does not exist!\n",
+ __func__);
+ return -EINVAL;
+ }
+
+ list_for_each_entry(temp_dev, &dvfs_info->dev_list, node) {
+ if (temp_dev->dev == target_dev)
+ break;
+ }
+
+ if (temp_dev->dev != target_dev) {
+ dev_warn(target_dev, "%s: target_dev does not exist!\n",
+ __func__);
+ return -EINVAL;
+ }
+
+ spin_lock(&temp_dev->user_lock);
+ plist_for_each_entry(tmp_user, &temp_dev->freq_user_list, node) {
+ if (tmp_user->dev == req_dev) {
+ dev_user = tmp_user;
+ break;
+ }
+ }
+
+ if (!dev_user) {
+ dev_user = kzalloc(sizeof(struct omap_dev_user_list),
+ GFP_ATOMIC);
+ if (!dev_user) {
+ dev_err(target_dev,
+ "%s: Unable to creat a new user for vdd_%s\n",
+ __func__, dvfs_info->voltdm->name);
+ spin_unlock(&temp_dev->user_lock);
+ return -ENOMEM;
+ }
+ dev_user->dev = req_dev;
+ } else {
+ plist_del(&dev_user->node, &temp_dev->freq_user_list);
+ }
+
+ plist_node_init(&dev_user->node, freq);
+ plist_add(&dev_user->node, &temp_dev->freq_user_list);
+ spin_unlock(&temp_dev->user_lock);
+ return 0;
+}
+
+/**
+ * _remove_freq_request() - Remove the requested device frequency
+ *
+ * @dvfs_info: omap_vdd_dvfs_info pointer for the required vdd
+ * @req_dev: device removing the request
+ * @target_dev: target device from which frequency request is being removed
+ *
+ * This removes a requested frequency from target device's frequency list.
+ *
+ * Returns 0 on success.
+ */
+static int _remove_freq_request(struct omap_vdd_dvfs_info *dvfs_info,
+ struct device *req_dev, struct device *target_dev)
+{
+ struct omap_dev_user_list *dev_user = NULL, *tmp_user;
+ int ret = 0;
+ struct omap_vdd_dev_list *temp_dev;
+
+ if (!dvfs_info || IS_ERR(dvfs_info)) {
+ dev_warn(target_dev, "%s: VDD specified does not exist!\n",
+ __func__);
+ return -EINVAL;
+ }
+
+
+ list_for_each_entry(temp_dev, &dvfs_info->dev_list, node) {
+ if (temp_dev->dev == target_dev)
+ break;
+ }
+
+ if (temp_dev->dev != target_dev) {
+ dev_warn(target_dev, "%s: target_dev does not exist!\n",
+ __func__);
+ return -EINVAL;
+ }
+
+ spin_lock(&temp_dev->user_lock);
+ plist_for_each_entry(tmp_user, &temp_dev->freq_user_list, node) {
+ if (tmp_user->dev == req_dev) {
+ dev_user = tmp_user;
+ break;
+ }
+ }
+
+ if (dev_user) {
+ plist_del(&dev_user->node, &temp_dev->freq_user_list);
+ } else {
+ dev_err(target_dev,
+ "%s: Unable to remove the user for vdd_%s\n",
+ __func__, dvfs_info->voltdm->name);
+ ret = -EINVAL;
+ }
+
+ spin_unlock(&temp_dev->user_lock);
+ kfree(dev_user);
+
+ return ret;
+}
+
+/**
+ * _dep_scan_table() - Scan a dependency table and mark for scaling
+ * @dev: device requesting the dependency scan (req_dev)
+ * @dep_info: dependency information (contains the table)
+ * @main_volt: voltage dependency to search for
+ *
+ * This runs down the table provided to find the match for main_volt
+ * provided and sets up a scale request for the dependent domain
+ * for the dependent voltage.
+ *
+ * Returns 0 if all went well.
+ */
+static int _dep_scan_table(struct device *dev,
+ struct omap_vdd_dep_info *dep_info, unsigned long main_volt)
+{
+ struct omap_vdd_dep_volt *dep_table = dep_info->dep_table;
+ struct device *target_dev;
+ struct omap_vdd_dvfs_info *tdvfs_info;
+ struct opp *opp;
+ int i, ret;
+ unsigned long dep_volt = 0, new_freq = 0;
+
+ if (!dep_table) {
+ dev_err(dev, "%s: deptable not present for vdd%s\n",
+ __func__, dep_info->name);
+ return -EINVAL;
+ }
+
+ /* Now scan through the the dep table for a match */
+ for (i = 0; i < dep_info->nr_dep_entries; i++) {
+ if (dep_table[i].main_vdd_volt == main_volt) {
+ dep_volt = dep_table[i].dep_vdd_volt;
+ break;
+ }
+ }
+ if (!dep_volt) {
+ dev_warn(dev, "%s: %ld volt map missing in vdd_%s\n",
+ __func__, main_volt, dep_info->name);
+ return -EINVAL;
+ }
+
+ /* populate voltdm if it is not present */
+ if (!dep_info->_dep_voltdm) {
+ dep_info->_dep_voltdm = voltdm_lookup(dep_info->name);
+ if (!dep_info->_dep_voltdm) {
+ dev_warn(dev, "%s: unable to get vdm%s\n",
+ __func__, dep_info->name);
+ return -ENODEV;
+ }
+ }
+
+ /* See if dep_volt is possible for the vdd*/
+ ret = _add_vdd_user(_voltdm_to_dvfs_info(dep_info->_dep_voltdm),
+ dev, dep_volt);
+ if (ret)
+ dev_err(dev, "%s: Failed to add dep to domain %s volt=%ld\n",
+ __func__, dep_info->name, dep_volt);
+
+ /* And also add corresponding freq request */
+ tdvfs_info = _voltdm_to_dvfs_info(dep_info->_dep_voltdm);
+ if (!tdvfs_info) {
+ dev_warn(dev, "%s: no dvfs_info\n",
+ __func__);
+ return -ENODEV;
+ }
+ target_dev = _dvfs_info_to_dev(tdvfs_info);
+ if (!target_dev) {
+ dev_warn(dev, "%s: no target_dev\n",
+ __func__);
+ return -ENODEV;
+ }
+
+ rcu_read_lock();
+ opp = _volt_to_opp(target_dev, dep_volt);
+ if (!IS_ERR(opp))
+ new_freq = opp_get_freq(opp);
+ rcu_read_unlock();
+
+ if (new_freq) {
+ ret = _add_freq_request(tdvfs_info, dev, target_dev, new_freq);
+ if (ret) {
+ dev_err(target_dev, "%s: freqadd(%s) failed %d[f=%ld,"
+ "v=%ld]\n", __func__, dev_name(dev),
+ i, new_freq, dep_volt);
+ return ret;
+ }
+ }
+
+ return ret;
+}
+
+/**
+ * _dep_scan_domains() - Scan dependency domains for a device
+ * @dev: device requesting the scan
+ * @vdd: vdd_info corresponding to the device
+ * @main_volt: voltage to scan for
+ *
+ * Since each domain *may* have multiple dependent domains, we scan
+ * through each of the dependent domains and invoke _dep_scan_table to
+ * scan each table for dependent domain for dependency scaling.
+ *
+ * This assumes that the dependent domain information is NULL entry terminated.
+ * Returns 0 if all went well.
+ */
+static int _dep_scan_domains(struct device *dev,
+ struct omap_vdd_info *vdd, unsigned long main_volt)
+{
+ struct omap_vdd_dep_info *dep_info = vdd->dep_vdd_info;
+ int ret = 0, r;
+
+ if (!dep_info) {
+ dev_dbg(dev, "%s: No dependent VDD\n", __func__);
+ return 0;
+ }
+
+ /* First scan through the mydomain->dep_domain list */
+ while (dep_info->nr_dep_entries) {
+ r = _dep_scan_table(dev, dep_info, main_volt);
+ /* Store last failed value */
+ ret = (r) ? r : ret;
+ dep_info++;
+ }
+
+ return ret;
+}
+
+/**
+ * _dep_scale_domains() - Cause a scale of all dependent domains
+ * @req_dev: device requesting the scale
+ * @req_vdd: vdd_info corresponding to the requesting device.
+ *
+ * This walks through every dependent domain and triggers a scale
+ * It is assumed that the corresponding scale handling for the
+ * domain translates this to freq and voltage scale operations as
+ * needed.
+ *
+ * Note: This is uses _dvfs_scale and one should be careful not to
+ * create a circular depedency (e.g. vdd_mpu->vdd_core->vdd->mpu)
+ * which can create deadlocks. No protection is provided to prevent
+ * this condition and a tree organization is assumed.
+ *
+ * Returns 0 if all went fine.
+ */
+static int _dep_scale_domains(struct device *req_dev,
+ struct omap_vdd_info *req_vdd)
+{
+ struct omap_vdd_dep_info *dep_info = req_vdd->dep_vdd_info;
+ int ret = 0, r;
+
+ if (!dep_info) {
+ dev_dbg(req_dev, "%s: No dependent VDD\n", __func__);
+ return 0;
+ }
+
+ /* First scan through the mydomain->dep_domain list */
+ while (dep_info->nr_dep_entries) {
+ struct voltagedomain *tvoltdm = dep_info->_dep_voltdm;
+
+ r = 0;
+ /* Scale it only if I have a voltdm mapped up for the dep */
+ if (tvoltdm) {
+ struct omap_vdd_dvfs_info *tdvfs_info;
+ struct device *target_dev;
+ tdvfs_info = _voltdm_to_dvfs_info(tvoltdm);
+ if (!tdvfs_info) {
+ dev_warn(req_dev, "%s: no dvfs_info\n",
+ __func__);
+ goto next;
+ }
+ target_dev = _dvfs_info_to_dev(tdvfs_info);
+ if (!target_dev) {
+ dev_warn(req_dev, "%s: no target_dev\n",
+ __func__);
+ goto next;
+ }
+ r = _dvfs_scale(req_dev, target_dev, tdvfs_info);
+next:
+ if (r)
+ dev_err(req_dev, "%s: dvfs_scale to %s =%d\n",
+ __func__, dev_name(target_dev), r);
+ }
+ /* Store last failed value */
+ ret = (r) ? r : ret;
+ dep_info++;
+ }
+
+ return ret;
+}
+
+/**
+ * _dvfs_scale() : Scale the devices associated with a voltage domain
+ * @req_dev: Device requesting the scale
+ * @target_dev: Device requesting to be scaled
+ * @tdvfs_info: omap_vdd_dvfs_info pointer for the target domain
+ *
+ * This runs through the list of devices associated with the
+ * voltage domain and scales the device rates to the one requested
+ * by the user or those corresponding to the new voltage of the
+ * voltage domain. Target voltage is the highest voltage in the vdd_user_list.
+ *
+ * Returns 0 on success else the error value.
+ */
+static int _dvfs_scale(struct device *req_dev, struct device *target_dev,
+ struct omap_vdd_dvfs_info *tdvfs_info)
+{
+ unsigned long curr_volt, new_volt;
+ int volt_scale_dir = DVFS_VOLT_SCALE_DOWN;
+ struct omap_vdd_dev_list *temp_dev;
+ struct plist_node *node;
+ int ret = 0;
+ struct voltagedomain *voltdm;
+ struct omap_vdd_info *vdd;
+ struct omap_volt_data *new_vdata;
+ struct omap_volt_data *curr_vdata;
+
+ voltdm = tdvfs_info->voltdm;
+ if (IS_ERR_OR_NULL(voltdm)) {
+ dev_err(target_dev, "%s: bad voltdm\n", __func__);
+ return -EINVAL;
+ }
+ vdd = voltdm->vdd;
+
+ /* Find the highest voltage being requested */
+ node = plist_last(&tdvfs_info->vdd_user_list);
+ new_volt = node->prio;
+
+ new_vdata = omap_voltage_get_voltdata(voltdm, new_volt);
+ if (IS_ERR_OR_NULL(new_vdata)) {
+ pr_err("%s:%s: Bad New voltage data for %ld\n",
+ __func__, voltdm->name, new_volt);
+ return PTR_ERR(new_vdata);
+ }
+ new_volt = omap_get_operation_voltage(new_vdata);
+ curr_vdata = omap_voltage_get_curr_vdata(voltdm);
+ if (IS_ERR_OR_NULL(curr_vdata)) {
+ pr_err("%s:%s: Bad Current voltage data\n",
+ __func__, voltdm->name);
+ return PTR_ERR(curr_vdata);
+ }
+
+ /* Disable smartreflex module across voltage and frequency scaling */
+ omap_sr_disable(voltdm);
+
+ /* Pick up the current voltage ONLY after ensuring no changes occur */
+ curr_volt = omap_vp_get_curr_volt(voltdm);
+ if (!curr_volt)
+ curr_volt = omap_get_operation_voltage(curr_vdata);
+
+ /* Make a decision to scale dependent domain based on nominal voltage */
+ if (omap_get_nominal_voltage(new_vdata) >
+ omap_get_nominal_voltage(curr_vdata)) {
+ ret = _dep_scale_domains(target_dev, vdd);
+ if (ret) {
+ dev_err(target_dev,
+ "%s: Error(%d)scale dependent with %ld volt\n",
+ __func__, ret, new_volt);
+ goto fail;
+ }
+ }
+
+ if (voltdm->abb && omap_get_nominal_voltage(new_vdata) >
+ omap_get_nominal_voltage(curr_vdata)) {
+ ret = omap_ldo_abb_pre_scale(voltdm, new_vdata);
+ if (ret) {
+ pr_err("%s: ABB prescale failed for vdd%s: %d\n",
+ __func__, voltdm->name, ret);
+ goto fail;
+ }
+ }
+
+ /* Now decide on switching OPP */
+ if (curr_volt == new_volt) {
+ volt_scale_dir = DVFS_VOLT_SCALE_NONE;
+ } else if (curr_volt < new_volt) {
+ ret = voltdm_scale(voltdm, new_vdata);
+ if (ret) {
+ dev_err(target_dev,
+ "%s: Unable to scale the %s to %ld volt\n",
+ __func__, voltdm->name, new_volt);
+ goto fail;
+ }
+ volt_scale_dir = DVFS_VOLT_SCALE_UP;
+ }
+
+ if (voltdm->abb && omap_get_nominal_voltage(new_vdata) >
+ omap_get_nominal_voltage(curr_vdata)) {
+ ret = omap_ldo_abb_post_scale(voltdm, new_vdata);
+ if (ret) {
+ pr_err("%s: ABB prescale failed for vdd%s: %d\n",
+ __func__, voltdm->name, ret);
+ goto fail;
+ }
+ }
+
+ /* Move all devices in list to the required frequencies */
+ list_for_each_entry(temp_dev, &tdvfs_info->dev_list, node) {
+ struct device *dev;
+ struct opp *opp;
+ unsigned long freq = 0;
+ int r;
+
+ dev = temp_dev->dev;
+ if (!plist_head_empty(&temp_dev->freq_user_list)) {
+ node = plist_last(&temp_dev->freq_user_list);
+ freq = node->prio;
+ } else {
+ /*
+ * Is the dev of dep domain target_device?
+ * we'd probably have a voltage request without
+ * a frequency dependency, scale appropriate frequency
+ * if there are none pending
+ */
+ if (target_dev == dev) {
+ rcu_read_lock();
+ opp = _volt_to_opp(dev, new_volt);
+ if (!IS_ERR(opp))
+ freq = opp_get_freq(opp);
+ rcu_read_unlock();
+ }
+ if (!freq)
+ continue;
+ }
+
+ if (freq == clk_get_rate(temp_dev->clk)) {
+ dev_dbg(dev, "%s: Already at the requested"
+ "rate %ld\n", __func__, freq);
+ continue;
+ }
+
+ r = clk_set_rate(temp_dev->clk, freq);
+ if (r < 0) {
+ dev_err(dev, "%s: clk set rate frq=%ld failed(%d)\n",
+ __func__, freq, r);
+ ret = r;
+ }
+ }
+
+ if (ret)
+ goto fail;
+
+ if (voltdm->abb && omap_get_nominal_voltage(new_vdata) <
+ omap_get_nominal_voltage(curr_vdata)) {
+ ret = omap_ldo_abb_pre_scale(voltdm, new_vdata);
+ if (ret) {
+ pr_err("%s: ABB prescale failed for vdd%s: %d\n",
+ __func__, voltdm->name, ret);
+ goto fail;
+ }
+ }
+
+ if (DVFS_VOLT_SCALE_DOWN == volt_scale_dir)
+ voltdm_scale(voltdm, new_vdata);
+
+ if (voltdm->abb && omap_get_nominal_voltage(new_vdata) <
+ omap_get_nominal_voltage(curr_vdata)) {
+ ret = omap_ldo_abb_post_scale(voltdm, new_vdata);
+ if (ret)
+ pr_err("%s: ABB postscale failed for vdd%s: %d\n",
+ __func__, voltdm->name, ret);
+ }
+
+ /* Make a decision to scale dependent domain based on nominal voltage */
+ if (omap_get_nominal_voltage(new_vdata) <
+ omap_get_nominal_voltage(curr_vdata)) {
+ _dep_scale_domains(target_dev, vdd);
+ }
+
+ /* Ensure that current voltage data pointer points to new volt */
+ if (curr_volt == new_volt && omap_get_nominal_voltage(new_vdata) !=
+ omap_get_nominal_voltage(curr_vdata)) {
+ voltdm->curr_volt = new_vdata;
+ omap_vp_update_errorgain(voltdm, new_vdata);
+ }
+
+ /* All clear.. go out gracefully */
+ goto out;
+
+fail:
+ pr_warning("%s: domain%s: No clean recovery available! could be bad!\n",
+ __func__, voltdm->name);
+out:
+ /* Re-enable Smartreflex module */
+ omap_sr_enable(voltdm, new_vdata);
+
+ return ret;
+}
+
+/* Public functions */
+
+/**
+ * omap_device_scale() - Set a new rate at which the device is to operate
+ * @req_dev: pointer to the device requesting the scaling.
+ * @target_dev: pointer to the device that is to be scaled
+ * @rate: the rnew rate for the device.
+ *
+ * This API gets the device opp table associated with this device and
+ * tries putting the device to the requested rate and the voltage domain
+ * associated with the device to the voltage corresponding to the
+ * requested rate. Since multiple devices can be assocciated with a
+ * voltage domain this API finds out the possible voltage the
+ * voltage domain can enter and then decides on the final device
+ * rate.
+ *
+ * Return 0 on success else the error value
+ */
+int omap_device_scale(struct device *req_dev, struct device *target_dev,
+ unsigned long rate)
+{
+ struct opp *opp;
+ unsigned long volt, freq = rate, new_freq = 0;
+ struct omap_vdd_dvfs_info *tdvfs_info;
+ struct platform_device *pdev;
+ struct omap_device *od;
+ struct device *dev;
+ int ret = 0;
+
+ pdev = container_of(target_dev, struct platform_device, dev);
+ if (IS_ERR_OR_NULL(pdev)) {
+ pr_err("%s: pdev is null!\n", __func__);
+ return -EINVAL;
+ }
+
+ od = container_of(pdev, struct omap_device, pdev);
+ if (IS_ERR_OR_NULL(od)) {
+ pr_err("%s: od is null!\n", __func__);
+ return -EINVAL;
+ }
+
+ if (!omap_pm_is_ready()) {
+ dev_dbg(target_dev, "%s: pm is not ready yet\n", __func__);
+ return -EBUSY;
+ }
+
+ /* Lock me to ensure cross domain scaling is secure */
+ mutex_lock(&omap_dvfs_lock);
+
+ rcu_read_lock();
+ opp = opp_find_freq_ceil(target_dev, &freq);
+ /* If we dont find a max, try a floor at least */
+ if (IS_ERR(opp))
+ opp = opp_find_freq_floor(target_dev, &freq);
+ if (IS_ERR(opp)) {
+ rcu_read_unlock();
+ dev_err(target_dev, "%s: Unable to find OPP for freq%ld\n",
+ __func__, rate);
+ ret = -ENODEV;
+ goto out;
+ }
+ volt = opp_get_voltage(opp);
+ rcu_read_unlock();
+
+ tdvfs_info = _dev_to_dvfs_info(target_dev);
+ if (IS_ERR_OR_NULL(tdvfs_info)) {
+ dev_err(target_dev, "%s: (req=%s) no vdd![f=%ld, v=%ld]\n",
+ __func__, dev_name(req_dev), freq, volt);
+ ret = -ENODEV;
+ goto out;
+ }
+
+ ret = _add_freq_request(tdvfs_info, req_dev, target_dev, freq);
+ if (ret) {
+ dev_err(target_dev, "%s: freqadd(%s) failed %d[f=%ld, v=%ld]\n",
+ __func__, dev_name(req_dev), ret, freq, volt);
+ goto out;
+ }
+
+ ret = _add_vdd_user(tdvfs_info, req_dev, volt);
+ if (ret) {
+ dev_err(target_dev, "%s: vddadd(%s) failed %d[f=%ld, v=%ld]\n",
+ __func__, dev_name(req_dev), ret, freq, volt);
+ _remove_freq_request(tdvfs_info, req_dev,
+ target_dev);
+ goto out;
+ }
+
+ /* Check for any dep domains and add the user request */
+ ret = _dep_scan_domains(target_dev, tdvfs_info->voltdm->vdd, volt);
+ if (ret) {
+ dev_err(target_dev,
+ "%s: Error in scan domains for vdd_%s\n",
+ __func__, tdvfs_info->voltdm->name);
+ goto out;
+ }
+
+ dev = _dvfs_info_to_dev(tdvfs_info);
+ if (!dev) {
+ dev_warn(dev, "%s: no target_dev\n",
+ __func__);
+ ret = -ENODEV;
+ goto out;
+ }
+
+ if (dev != target_dev) {
+ rcu_read_lock();
+ opp = _volt_to_opp(dev, volt);
+ if (!IS_ERR(opp))
+ new_freq = opp_get_freq(opp);
+ rcu_read_unlock();
+ if (new_freq) {
+ ret = _add_freq_request(tdvfs_info, req_dev, dev,
+ new_freq);
+ if (ret) {
+ dev_err(target_dev, "%s: freqadd(%s) failed %d"
+ "[f=%ld, v=%ld]\n", __func__,
+ dev_name(req_dev), ret, freq, volt);
+ goto out;
+ }
+ }
+ }
+
+ /* Do the actual scaling */
+ ret = _dvfs_scale(req_dev, target_dev, tdvfs_info);
+ if (ret) {
+ dev_err(target_dev, "%s: scale by %s failed %d[f=%ld, v=%ld]\n",
+ __func__, dev_name(req_dev), ret, freq, volt);
+ _remove_freq_request(tdvfs_info, req_dev,
+ target_dev);
+ _remove_vdd_user(tdvfs_info, target_dev);
+ /* Fall through */
+ }
+ /* Fall through */
+out:
+ mutex_unlock(&omap_dvfs_lock);
+ return ret;
+}
+EXPORT_SYMBOL(omap_device_scale);
+
+#ifdef CONFIG_PM_DEBUG
+static int dvfs_dump_vdd(struct seq_file *sf, void *unused)
+{
+ int k;
+ struct omap_vdd_dvfs_info *dvfs_info;
+ struct omap_vdd_dev_list *tdev;
+ struct omap_dev_user_list *duser;
+ struct omap_vdd_user_list *vuser;
+ struct omap_vdd_info *vdd;
+ struct omap_vdd_dep_info *dep_info;
+ struct voltagedomain *voltdm;
+ struct omap_volt_data *volt_data;
+ int anyreq;
+ int anyreq2;
+
+ dvfs_info = (struct omap_vdd_dvfs_info *)sf->private;
+ if (IS_ERR_OR_NULL(dvfs_info)) {
+ pr_err("%s: NO DVFS?\n", __func__);
+ return -EINVAL;
+ }
+
+ voltdm = dvfs_info->voltdm;
+ if (IS_ERR_OR_NULL(voltdm)) {
+ pr_err("%s: NO voltdm?\n", __func__);
+ return -EINVAL;
+ }
+
+ vdd = voltdm->vdd;
+ if (IS_ERR_OR_NULL(vdd)) {
+ pr_err("%s: NO vdd data?\n", __func__);
+ return -EINVAL;
+ }
+
+ seq_printf(sf, "vdd_%s\n", voltdm->name);
+ mutex_lock(&omap_dvfs_lock);
+ spin_lock(&dvfs_info->user_lock);
+
+ seq_printf(sf, "|- voltage requests\n| |\n");
+ anyreq = 0;
+ plist_for_each_entry(vuser, &dvfs_info->vdd_user_list, node) {
+ seq_printf(sf, "| |-%d: %s:%s\n",
+ vuser->node.prio,
+ dev_driver_string(vuser->dev), dev_name(vuser->dev));
+ anyreq = 1;
+ }
+
+ spin_unlock(&dvfs_info->user_lock);
+
+ if (!anyreq)
+ seq_printf(sf, "| `-none\n");
+ else
+ seq_printf(sf, "| X\n");
+ seq_printf(sf, "|\n");
+
+ seq_printf(sf, "|- frequency requests\n| |\n");
+ anyreq2 = 0;
+ list_for_each_entry(tdev, &dvfs_info->dev_list, node) {
+ anyreq = 0;
+ seq_printf(sf, "| |- %s:%s\n",
+ dev_driver_string(tdev->dev), dev_name(tdev->dev));
+ spin_lock(&tdev->user_lock);
+ plist_for_each_entry(duser, &tdev->freq_user_list, node) {
+ seq_printf(sf, "| | |-%d: %s:%s\n",
+ duser->node.prio,
+ dev_driver_string(duser->dev),
+ dev_name(duser->dev));
+ anyreq = 1;
+ }
+
+ spin_unlock(&tdev->user_lock);
+
+ if (!anyreq)
+ seq_printf(sf, "| | `-none\n");
+ else
+ seq_printf(sf, "| | X\n");
+ anyreq2 = 1;
+ }
+ if (!anyreq2)
+ seq_printf(sf, "| `-none\n");
+ else
+ seq_printf(sf, "| X\n");
+
+ volt_data = vdd->volt_data;
+ seq_printf(sf, "|- Supported voltages\n| |\n");
+ anyreq = 0;
+ while (volt_data && volt_data->volt_nominal) {
+ seq_printf(sf, "| |-%d\n", volt_data->volt_nominal);
+ anyreq = 1;
+ volt_data++;
+ }
+ if (!anyreq)
+ seq_printf(sf, "| `-none\n");
+ else
+ seq_printf(sf, "| X\n");
+
+ dep_info = vdd->dep_vdd_info;
+ seq_printf(sf, "`- voltage dependencies\n |\n");
+ anyreq = 0;
+ while (dep_info && dep_info->nr_dep_entries) {
+ struct omap_vdd_dep_volt *dep_table = dep_info->dep_table;
+
+ seq_printf(sf, " |-on vdd_%s\n", dep_info->name);
+
+ for (k = 0; k < dep_info->nr_dep_entries; k++) {
+ seq_printf(sf, " | |- %d => %d\n",
+ dep_table[k].main_vdd_volt,
+ dep_table[k].dep_vdd_volt);
+ }
+
+ anyreq = 1;
+ dep_info++;
+ }
+
+ if (!anyreq)
+ seq_printf(sf, " `- none\n");
+ else
+ seq_printf(sf, " X X\n");
+
+ mutex_unlock(&omap_dvfs_lock);
+ return 0;
+}
+
+static int dvfs_dbg_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, dvfs_dump_vdd, inode->i_private);
+}
+
+static struct file_operations debugdvfs_fops = {
+ .open = dvfs_dbg_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static struct dentry __initdata *dvfsdebugfs_dir;
+
+static void __init dvfs_dbg_init(struct omap_vdd_dvfs_info *dvfs_info)
+{
+ struct dentry *ddir;
+
+ /* create a base dir */
+ if (!dvfsdebugfs_dir)
+ dvfsdebugfs_dir = debugfs_create_dir("dvfs", NULL);
+ if (IS_ERR_OR_NULL(dvfsdebugfs_dir)) {
+ WARN_ONCE("%s: Unable to create base DVFS dir\n", __func__);
+ return;
+ }
+
+ if (IS_ERR_OR_NULL(dvfs_info->voltdm)) {
+ pr_err("%s: no voltdm\n", __func__);
+ return;
+ }
+
+ ddir = debugfs_create_dir(dvfs_info->voltdm->name, dvfsdebugfs_dir);
+ if (IS_ERR_OR_NULL(ddir)) {
+ pr_warning("%s: unable to create subdir %s\n", __func__,
+ dvfs_info->voltdm->name);
+ return;
+ }
+
+ debugfs_create_file("info", S_IRUGO, ddir,
+ (void *)dvfs_info, &debugdvfs_fops);
+}
+#else /* CONFIG_PM_DEBUG */
+static inline void dvfs_dbg_init(struct omap_vdd_dvfs_info *dvfs_info)
+{
+ return;
+}
+#endif /* CONFIG_PM_DEBUG */
+
+/**
+ * omap_dvfs_register_device - Add a parent device into dvfs managed list
+ * @dev: Device to be added
+ * @voltdm_name: Name of the voltage domain for the device
+ * @clk_name: Name of the clock for the device
+ *
+ * This function adds a given device into user_list of corresponding
+ * vdd's omap_vdd_dvfs_info strucure. This list is traversed to scale
+ * frequencies of all the devices on a given vdd.
+ *
+ * Returns 0 on success.
+ */
+int __init omap_dvfs_register_device(struct device *dev, char *voltdm_name,
+ char *clk_name)
+{
+ struct omap_vdd_dev_list *temp_dev;
+ struct omap_vdd_dvfs_info *dvfs_info;
+ struct clk *clk = NULL;
+ struct voltagedomain *voltdm;
+ int ret = 0;
+
+ if (!voltdm_name) {
+ dev_err(dev, "%s: Bad voltdm name!\n", __func__);
+ return -EINVAL;
+ }
+ if (!clk_name) {
+ dev_err(dev, "%s: Bad clk name!\n", __func__);
+ return -EINVAL;
+ }
+
+ /* Lock me to secure structure changes */
+ mutex_lock(&omap_dvfs_lock);
+
+ voltdm = voltdm_lookup(voltdm_name);
+ if (!voltdm) {
+ dev_warn(dev, "%s: unable to find voltdm %s!\n",
+ __func__, voltdm_name);
+ ret = -EINVAL;
+ goto out;
+ }
+ dvfs_info = _voltdm_to_dvfs_info(voltdm);
+ if (!dvfs_info) {
+ dvfs_info = kzalloc(sizeof(struct omap_vdd_dvfs_info),
+ GFP_KERNEL);
+ if (!dvfs_info) {
+ dev_warn(dev, "%s: unable to alloc memory!\n",
+ __func__);
+ ret = -ENOMEM;
+ goto out;
+ }
+ dvfs_info->voltdm = voltdm;
+
+ /* Init the plist */
+ spin_lock_init(&dvfs_info->user_lock);
+ plist_head_init(&dvfs_info->vdd_user_list);
+ /* Init the device list */
+ INIT_LIST_HEAD(&dvfs_info->dev_list);
+
+ list_add(&dvfs_info->node, &omap_dvfs_info_list);
+
+ dvfs_dbg_init(dvfs_info);
+ }
+
+ /* If device already added, we dont need to do more.. */
+ list_for_each_entry(temp_dev, &dvfs_info->dev_list, node) {
+ if (temp_dev->dev == dev)
+ goto out;
+ }
+
+ temp_dev = kzalloc(sizeof(struct omap_vdd_dev_list), GFP_KERNEL);
+ if (!temp_dev) {
+ dev_err(dev, "%s: Unable to creat a new device for vdd_%s\n",
+ __func__, dvfs_info->voltdm->name);
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ clk = clk_get(dev, clk_name);
+ if (IS_ERR_OR_NULL(clk)) {
+ dev_warn(dev, "%s: Bad clk pointer!\n", __func__);
+ kfree(temp_dev);
+ ret = -EINVAL;
+ goto out;
+ }
+
+ /* Initialize priority ordered list */
+ spin_lock_init(&temp_dev->user_lock);
+ plist_head_init(&temp_dev->freq_user_list);
+
+ temp_dev->dev = dev;
+ temp_dev->clk = clk;
+ list_add_tail(&temp_dev->node, &dvfs_info->dev_list);
+
+ /* Fall through */
+out:
+ mutex_unlock(&omap_dvfs_lock);
+ return ret;
+}