/* * OMAP3/OMAP4 DVFS Management Routines * * Author: Vishwanath BS * * Copyright (C) 2011 Texas Instruments, Inc. * Vishwanath BS * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #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 * should be at voltage v1 when VDD 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 he 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. 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. * * * 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 * @is_scaling: flag to store information about scaling in progress or not * this flag is already protected by the global mutex. * * 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; bool is_scaling; }; static LIST_HEAD(omap_dvfs_info_list); static 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 returns ERR_PTR in case of error and should be handled * using IS_ERR. 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)) 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; int i; unsigned long dep_volt = 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*/ i = _add_vdd_user(_voltdm_to_dvfs_info(dep_info->_dep_voltdm), dev, dep_volt); if (i) dev_err(dev, "%s: Failed to add dep to domain %s volt=%ld\n", __func__, dep_info->name, dep_volt); return i; } /** * _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; voltdm = tdvfs_info->voltdm; if (IS_ERR_OR_NULL(voltdm)) { dev_err(target_dev, "%s: bad voltdm\n", __func__); return -EINVAL; } vdd = voltdm->vdd; /* Mark that we are scaling for this device */ tdvfs_info->is_scaling = true; /* let the other CPU know as well */ smp_wmb(); /* Find the highest voltage being requested */ node = plist_last(&tdvfs_info->vdd_user_list); new_volt = node->prio; curr_volt = voltdm_get_voltage(voltdm); /* Disable smartreflex module across voltage and frequency scaling */ omap_sr_disable(voltdm); if (curr_volt == new_volt) { volt_scale_dir = DVFS_VOLT_SCALE_NONE; } else if (curr_volt < new_volt) { ret = voltdm_scale(voltdm, new_volt); if (ret) { dev_err(target_dev, "%s: Unable to scale the %s to %ld volt\n", __func__, voltdm->name, new_volt); goto out; } volt_scale_dir = DVFS_VOLT_SCALE_UP; } /* if we fail scale for dependent domains, go back to prev state */ ret = _dep_scan_domains(target_dev, vdd, new_volt); if (ret) { dev_err(target_dev, "%s: Error in scan domains for vdd_%s\n", __func__, voltdm->name); goto fail; } /* unless we are moving down, scale dependents before we shift freq */ if (!(DVFS_VOLT_SCALE_DOWN == volt_scale_dir)) { 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; } } /* 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 { /* dep domain? we'd probably have a voltage request */ 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 (DVFS_VOLT_SCALE_DOWN == volt_scale_dir) { voltdm_scale(voltdm, new_volt); _dep_scale_domains(target_dev, vdd); } /* 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); /* Mark done */ tdvfs_info->is_scaling = false; /* let the other CPU know as well */ smp_wmb(); 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; struct omap_vdd_dvfs_info *tdvfs_info; struct platform_device *pdev; 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; } 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; } /* 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); /** * omap_dvfs_is_scaling() - Tells the caller if the domain is scaling * @voltdm: voltage domain we are interested in * * Returns true if the domain is in the middle of scale operation, * returns false if there is no scale operation is in progress or an * invalid parameter was passed. */ bool omap_dvfs_is_scaling(struct voltagedomain *voltdm) { struct omap_vdd_dvfs_info *dvfs_info; if (IS_ERR_OR_NULL(voltdm)) { pr_err("%s: bad voltdm\n", __func__); return false; } dvfs_info = _voltdm_to_dvfs_info(voltdm); if (IS_ERR_OR_NULL(dvfs_info)) { pr_err("%s: no dvfsinfo for voltdm %s\n", __func__, voltdm->name); return false; } return dvfs_info->is_scaling; } EXPORT_SYMBOL(omap_dvfs_is_scaling); #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; }