/* * 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 #include #include #include #include #include #include #include #include /* Interface documentation is in mach/omap-pm.h */ #include #include #include #include "../mach-omap2/powerdomain.h" #include "../mach-omap2/dvfs.h" #include "../mach-omap2/pm.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 platform_device *pdev; struct users *user; int found = 0; int ret; pdev = container_of(dev, struct platform_device, dev); 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("OMAP-PM: Invalid Device Structure for %s\n", pdev->name); 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; } /** * get_tput_level - Gets the current requested interconnect bandwidth in KiB/s * * Returns the current level of interconnect throughput. * */ static unsigned long get_tput_level(void) { unsigned long ret; mutex_lock(&bus_tput->throughput_mutex); ret = bus_tput->target_level; mutex_unlock(&bus_tput->throughput_mutex); return ret; } int omap_pm_apply_min_bus_tput_helper_l(void) { int ret; unsigned long target_level; struct device *l3_dev; static struct device dummy_l3_dev = { .init_name = "omap_pm_set_min_bus_tput", }; l3_dev = omap2_get_l3_device(); if (!l3_dev) { pr_err("Unable to get l3 device pointer"); return -EINVAL; } target_level = get_tput_level(); /* 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) #ifdef CONFIG_OMAP4_DPLL_CASCADING pr_debug("Failed: change interconnect bandwidth to %ld\n", target_level); #else pr_err("Failed: change interconnect bandwidth to %ld\n", target_level); #endif return ret; } int omap_pm_apply_min_bus_tput_helper(void) { int ret; mutex_lock(&bus_tput_mutex); ret = omap_pm_apply_min_bus_tput_helper_l(); mutex_unlock(&bus_tput_mutex); return ret; } int omap_pm_set_min_bus_tput_helper(struct device *dev, u8 agent_id, long r) { int ret = 0; mutex_lock(&bus_tput_mutex); if (r == -1) remove_req_tput(dev); else add_req_tput(dev, r); /* Don't bother to attempt the device scale if the power management * subsystem has not been initialized yet since it will just fail. PM * will call back after it has come up to enforce any pending tput * constraints. */ if (omap_pm_is_ready()) ret = omap_pm_apply_min_bus_tput_helper_l(); 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; }