blob: 2fdd7af49a4dd365a80a2891fbcbcd9e0df7616b [file] [log] [blame]
/*
* omap-pm.c - OMAP power management interface
*
* Copyright (C) 2008-2011 Texas Instruments, Inc.
* Copyright (C) 2008-2009 Nokia Corporation
* Vishwanath BS
*
* This code is based on plat-omap/omap-pm-noop.c.
*
* Interface developed by (in alphabetical order):
* Karthik Dasu, Tony Lindgren, Rajendra Nayak, Sakari Poussa, Veeramanikandan
* Raju, Anand Sawant, Igor Stoppa, Paul Walmsley, Richard Woodruff
*/
#undef DEBUG
#include <linux/init.h>
#include <linux/cpufreq.h>
#include <linux/device.h>
#include <linux/errno.h>
#include <linux/err.h>
#include <linux/slab.h>
#include <linux/list.h>
#include <linux/debugfs.h>
#include <linux/seq_file.h>
/* Interface documentation is in mach/omap-pm.h */
#include <plat/omap-pm.h>
#include <plat/omap_device.h>
#include <plat/common.h>
#include "../mach-omap2/powerdomain.h"
#include "../mach-omap2/dvfs.h"
#include "omap-pm-helper.h"
struct omap_opp *dsp_opps;
struct omap_opp *mpu_opps;
struct omap_opp *l3_opps;
static DEFINE_MUTEX(bus_tput_mutex);
static DEFINE_MUTEX(mpu_tput_mutex);
static DEFINE_MUTEX(mpu_lat_mutex);
/* Used to model a Interconnect Throughput */
static struct interconnect_tput {
/* Total no of users at any point of interconnect */
u8 no_of_users;
/* List of all the current users for interconnect */
struct list_head users_list;
struct list_head node;
/* Protect interconnect throughput */
struct mutex throughput_mutex;
/* Target level for interconnect throughput */
unsigned long target_level;
} *bus_tput;
/* Used to represent a user of a interconnect throughput */
struct users {
/* Device pointer used to uniquely identify the user */
struct device *dev;
struct list_head node;
/* Current level as requested for interconnect throughput by the user */
u32 level;
};
/* Private/Internal Functions */
/**
* user_lookup - look up a user by its device pointer, return a pointer
* @dev: The device to be looked up
*
* Looks for a interconnect user by its device pointer. Returns a
* pointer to
* the struct users if found, else returns NULL.
*/
static struct users *user_lookup(struct device *dev)
{
struct users *usr, *tmp_usr;
usr = NULL;
list_for_each_entry(tmp_usr, &bus_tput->users_list, node) {
if (tmp_usr->dev == dev) {
usr = tmp_usr;
break;
}
}
return usr;
}
/**
* get_user - gets a new users_list struct dynamically
*
* This function allocates dynamcially the user node
* Returns a pointer to users struct on success. On dynamic allocation
* failure
* returns a ERR_PTR(-ENOMEM).
*/
static struct users *get_user(void)
{
struct users *user;
user = kmalloc(sizeof(struct users), GFP_KERNEL);
if (!user) {
pr_err("%s FATAL ERROR: kmalloc failed\n", __func__);
return ERR_PTR(-ENOMEM);
}
return user;
}
#ifdef CONFIG_PM_DEBUG
static int pm_dbg_show_tput(struct seq_file *s, void *unused)
{
struct users *usr;
mutex_lock(&bus_tput->throughput_mutex);
list_for_each_entry(usr, &bus_tput->users_list, node)
seq_printf(s, "%s: %u\n", dev_name(usr->dev),
usr->level);
mutex_unlock(&bus_tput->throughput_mutex);
return 0;
}
static int pm_dbg_open(struct inode *inode, struct file *file)
{
return single_open(file, pm_dbg_show_tput,
&inode->i_private);
}
static const struct file_operations tputdebugfs_fops = {
.open = pm_dbg_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
#endif
/**
* omap_bus_tput_init - Initializes the interconnect throughput
* userlist
* Allocates memory for global throughput variable dynamically.
* Intializes Userlist, no. of users and throughput target level.
* Returns 0 on sucess, else returns EINVAL if memory
* allocation fails.
*/
static int __init omap_bus_tput_init(void)
{
bus_tput = kmalloc(sizeof(struct interconnect_tput), GFP_KERNEL);
if (!bus_tput) {
pr_err("%s FATAL ERROR: kmalloc failed\n", __func__);
return -EINVAL;
}
INIT_LIST_HEAD(&bus_tput->users_list);
mutex_init(&bus_tput->throughput_mutex);
bus_tput->no_of_users = 0;
bus_tput->target_level = 0;
#ifdef CONFIG_PM_DEBUG
(void) debugfs_create_file("tput", S_IRUGO,
NULL, (void *)bus_tput, &tputdebugfs_fops);
#endif
return 0;
}
/**
* add_req_tput - Request for a required level by a device
* @dev: Uniquely identifes the caller
* @level: The requested level for the interconnect bandwidth in KiB/s
*
* This function recomputes the target level of the interconnect
* bandwidth
* based on the level requested by all the users.
* Multiple calls to this function by the same device will
* replace the previous level requested
* Returns the updated level of interconnect throughput.
* In case of Invalid dev or user pointer, it returns 0.
*/
static unsigned long add_req_tput(struct device *dev, unsigned long level)
{
int ret;
struct users *user;
if (!dev) {
pr_err("Invalid dev pointer\n");
ret = 0;
}
mutex_lock(&bus_tput->throughput_mutex);
user = user_lookup(dev);
if (user == NULL) {
user = get_user();
if (IS_ERR(user)) {
pr_err("Couldn't get user from the list to"
"add new throughput constraint");
ret = 0;
goto unlock;
}
bus_tput->target_level += level;
bus_tput->no_of_users++;
user->dev = dev;
list_add(&user->node, &bus_tput->users_list);
user->level = level;
} else {
bus_tput->target_level -= user->level;
bus_tput->target_level += level;
user->level = level;
}
ret = bus_tput->target_level;
unlock:
mutex_unlock(&bus_tput->throughput_mutex);
return ret;
}
/**
* remove_req_tput - Release a previously requested level of
* a throughput level for interconnect
* @dev: Device pointer to dev
*
* This function recomputes the target level of the interconnect
* throughput after removing
* the level requested by the user.
* Returns 0, if the dev structure is invalid
* else returns modified interconnect throughput rate.
*/
static unsigned long remove_req_tput(struct device *dev)
{
struct users *user;
int found = 0;
int ret;
mutex_lock(&bus_tput->throughput_mutex);
list_for_each_entry(user, &bus_tput->users_list, node) {
if (user->dev == dev) {
found = 1;
break;
}
}
if (!found) {
/* No such user exists */
pr_err("Invalid Device Structure\n");
ret = 0;
goto unlock;
}
bus_tput->target_level -= user->level;
bus_tput->no_of_users--;
list_del(&user->node);
kfree(user);
ret = bus_tput->target_level;
unlock:
mutex_unlock(&bus_tput->throughput_mutex);
return ret;
}
int omap_pm_set_min_bus_tput_helper(struct device *dev, u8 agent_id, long r)
{
int ret = 0;
struct device *l3_dev;
static struct device dummy_l3_dev = {
.init_name = "omap_pm_set_min_bus_tput",
};
unsigned long target_level = 0;
mutex_lock(&bus_tput_mutex);
l3_dev = omap2_get_l3_device();
if (!l3_dev) {
pr_err("Unable to get l3 device pointer");
ret = -EINVAL;
goto unlock;
}
if (r == -1)
target_level = remove_req_tput(dev);
else
target_level = add_req_tput(dev, r);
/* Convert the throughput(in KiB/s) into Hz. */
target_level = (target_level * 1000) / 4;
ret = omap_device_scale(&dummy_l3_dev, l3_dev, target_level);
if (ret)
pr_err("Failed: change interconnect bandwidth to %ld\n",
target_level);
unlock:
mutex_unlock(&bus_tput_mutex);
return ret;
}
int omap_pm_set_max_dev_wakeup_lat_helper(struct device *req_dev,
struct device *dev, long t)
{
struct omap_device *odev;
struct powerdomain *pwrdm_dev;
struct platform_device *pdev;
int ret = 0;
if (!req_dev || !dev || t < -1) {
WARN(1, "OMAP PM: %s: invalid parameter(s)", __func__);
return -EINVAL;
};
/* Look for the devices Power Domain */
pdev = container_of(dev, struct platform_device, dev);
/* Try to catch non platform devices. */
if (pdev->name == NULL) {
pr_err("OMAP-PM: Error: platform device not valid\n");
return -EINVAL;
}
odev = to_omap_device(pdev);
if (odev) {
pwrdm_dev = omap_device_get_pwrdm(odev);
} else {
pr_err("OMAP-PM: Error: Could not find omap_device for %s\n",
pdev->name);
return -EINVAL;
}
/* Catch devices with undefined powerdomains. */
if (!pwrdm_dev) {
pr_err("OMAP-PM: Error: could not find parent pwrdm for %s\n",
pdev->name);
return -EINVAL;
}
if (t == -1)
ret = pwrdm_wakeuplat_release_constraint(pwrdm_dev, req_dev);
else
ret = pwrdm_wakeuplat_set_constraint(pwrdm_dev, req_dev, t);
return ret;
}
/* Must be called after clock framework is initialized */
int __init omap_pm_if_init_helper(void)
{
int ret;
ret = omap_bus_tput_init();
if (ret)
pr_err("Failed: init of interconnect bandwidth users list\n");
return ret;
}