From patchwork Wed Dec 9 06:17:07 2009 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Nishanth Menon X-Patchwork-Id: 65885 X-Patchwork-Delegate: paul@pwsan.com Received: from vger.kernel.org (vger.kernel.org [209.132.176.167]) by demeter.kernel.org (8.14.2/8.14.2) with ESMTP id nB96Hon0003474 for ; Wed, 9 Dec 2009 06:17:52 GMT Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1755981AbZLIGRb (ORCPT ); Wed, 9 Dec 2009 01:17:31 -0500 Received: (majordomo@vger.kernel.org) by vger.kernel.org id S1754337AbZLIGRb (ORCPT ); Wed, 9 Dec 2009 01:17:31 -0500 Received: from arroyo.ext.ti.com ([192.94.94.40]:36537 "EHLO arroyo.ext.ti.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1755590AbZLIGRY (ORCPT ); Wed, 9 Dec 2009 01:17:24 -0500 Received: from dlep36.itg.ti.com ([157.170.170.91]) by arroyo.ext.ti.com (8.13.7/8.13.7) with ESMTP id nB96HPEN022378 (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-SHA bits=256 verify=NO); Wed, 9 Dec 2009 00:17:25 -0600 Received: from legion.dal.design.ti.com (localhost [127.0.0.1]) by dlep36.itg.ti.com (8.13.8/8.13.8) with ESMTP id nB96HO3s010055; Wed, 9 Dec 2009 00:17:24 -0600 (CST) Received: from senorita (senorita.am.dhcp.ti.com [128.247.75.1]) by legion.dal.design.ti.com (8.11.7p1+Sun/8.11.7) with ESMTP id nB96HNZ06315; Wed, 9 Dec 2009 00:17:23 -0600 (CST) Received: by senorita (Postfix, from userid 1000) id 8EC78C184; Wed, 9 Dec 2009 00:17:23 -0600 (CST) From: Nishanth Menon To: Kevin Hilman Cc: linux-omap , Nishanth Menon , Benoit Cousson , Eduardo Valentin , Madhusudhan Chikkature Rajashekar , Paul Walmsley , Romit Dasgupta , Santosh Shilimkar , Sergio Alberto Aguirre Rodriguez , Tero Kristo , Thara Gopinath , Vishwanath Sripathy , Sanjeev Premi , Kevin Hilman Subject: [PATCH 02/10 V4] omap3: pm: introduce opp accessor functions Date: Wed, 9 Dec 2009 00:17:07 -0600 Message-Id: <1260339435-20294-3-git-send-email-nm@ti.com> X-Mailer: git-send-email 1.6.3.3 In-Reply-To: <1260339435-20294-2-git-send-email-nm@ti.com> References: <1260339435-20294-1-git-send-email-nm@ti.com> <1260339435-20294-2-git-send-email-nm@ti.com> Sender: linux-omap-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-omap@vger.kernel.org diff --git a/arch/arm/plat-omap/Makefile b/arch/arm/plat-omap/Makefile index 95f8413..e9cf601 100644 --- a/arch/arm/plat-omap/Makefile +++ b/arch/arm/plat-omap/Makefile @@ -12,6 +12,9 @@ obj- := # OCPI interconnect support for 1710, 1610 and 5912 obj-$(CONFIG_ARCH_OMAP16XX) += ocpi.o +# OPP support in (OMAP3+ only at the moment) +obj-$(CONFIG_ARCH_OMAP3) += opp.o + # omap_device support (OMAP2+ only at the moment) obj-$(CONFIG_ARCH_OMAP2) += omap_device.o obj-$(CONFIG_ARCH_OMAP3) += omap_device.o diff --git a/arch/arm/plat-omap/include/plat/opp.h b/arch/arm/plat-omap/include/plat/opp.h new file mode 100644 index 0000000..341c02b --- /dev/null +++ b/arch/arm/plat-omap/include/plat/opp.h @@ -0,0 +1,230 @@ +/* + * OMAP OPP Interface + * + * Copyright (C) 2009 Texas Instruments Incorporated. + * Nishanth Menon + * Copyright (C) 2009 Deep Root Systems, LLC. + * Kevin Hilman + * + * 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. + */ +#ifndef __ASM_ARM_OMAP_OPP_H +#define __ASM_ARM_OMAP_OPP_H + +/** + * struct omap_opp - OMAP OPP description structure + * @enabled: true/false - marking this OPP as enabled/disabled + * @rate: Frequency in hertz + * @opp_id: (DEPRECATED) opp identifier + * @vsel: Voltage in volt processor level(this usage is + * DEPRECATED to use Voltage in microvolts in future) + * uV = ((vsel * 12.5) + 600) * 1000 + * + * This structure stores the OPP information for a given domain. + * Due to legacy reasons, this structure is currently exposed and + * will soon be removed elsewhere and will only be used as a handle + * from the OPP internal referencing mechanism + */ +struct omap_opp { + bool enabled; + unsigned long rate; + u8 opp_id __deprecated; + u16 vsel; +}; + +/** + * opp_get_voltage() - Gets the voltage corresponding to an opp + * @opp: opp for which voltage has to be returned for + * + * Return voltage in micro volt corresponding to the opp, else + * return 0 + */ +unsigned long opp_get_voltage(const struct omap_opp *opp); + +/** + * opp_get_freq() - Gets the frequency corresponding to an opp + * @opp: opp for which frequency has to be returned for + * + * Return frequency in hertz corresponding to the opp, else + * return 0 + */ +unsigned long opp_get_freq(const struct omap_opp *opp); + +/** + * opp_get_opp_count() - Get number of opps enabled in the opp list + * @num: returns the number of opps + * @oppl: opp list + * + * This functions returns the number of opps if there are any OPPs enabled, + * else returns corresponding error value. + */ +int opp_get_opp_count(const struct omap_opp *oppl); + +/** + * opp_find_freq_exact() - search for an exact frequency + * @oppl: OPP list + * @freq: frequency to search for + * @enabled: enabled/disabled OPP to search for + * + * searches for the 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. + * + * Note enabled is a modifier for the search. if enabled=true, then the match is + * for exact matching frequency and is enabled. if true, the match is for exact + * frequency which is disabled. + */ +struct omap_opp *opp_find_freq_exact(struct omap_opp *oppl, + unsigned long freq, bool enabled); + +#define OPP_SEARCH_HIGH (0 << 1) +#define OPP_SEARCH_LOW (1 << 1) +/** + * opp_find_freq_approx() - Search for an rounded freq + * @oppl: Starting list + * @freq: Start frequency + * @dir_flag: Search direction + * OPP_SEARCH_HIGH - search for next highest freq + * OPP_SEARCH_LOW - search for next lowest freq + * + * Search for the higher/lower *enabled* OPP from a starting freq + * from a start opp list. + * + * Returns *opp and *freq is populated with the next match, + * else returns NULL + * opp if found, else returns ERR_PTR in case of error. + * + * Example usages: + * * find match/next highest available frequency + * freq = 350000; + * opp = opp_find_freq_approx(oppl, &freq, OPP_SEARCH_HIGH))) + * if (IS_ERR(opp)) + * pr_err ("unable to find a higher frequency\n"); + * else + * pr_info("match freq = %ld\n", freq); + * + * * find match/next lowest available frequency + * freq = 350000; + * opp = opp_find_freq_approx(oppl, &freq, OPP_SEARCH_LOW))) + * if (IS_ERR(opp)) + * pr_err ("unable to find a lower frequency\n"); + * else + * pr_info("match freq = %ld\n", freq); + * + * * print all supported frequencies in descending order * + * opp = oppl; + * freq = ULONG_MAX; + * while (!IS_ERR(opp = opp_find_freq_approx(opp, &freq, + * OPP_SEARCH_LOW))) { + * pr_info("freq = %ld\n", freq); + * freq--; * for next lower match * + * } + * + * * print all supported frequencies in ascending order * + * opp = oppl; + * freq = 0; + * while (!IS_ERR(opp = opp_find_freq_approx(opp, &freq, + * OPP_SEARCH_HIGH))) { + * pr_info("freq = %ld\n", freq); + * freq++; * for next higher match * + * } + * + * NOTE: if we set freq as ULONG_MAX and search low, we get the highest enabled + * frequency + */ +struct omap_opp *opp_find_freq_approx(struct omap_opp *oppl, + unsigned long *freq, u8 dir_flag); + +/** + * struct omap_opp_def - OMAP OPP Definition + * @enabled: True/false - is this OPP enabled/disabled by default + * @freq: Frequency in hertz corresponding to this OPP + * @u_volt: Nominal voltage in microvolts corresponding to this OPP + * + * OMAP SOCs have a standard set of tuples consisting of frequency and voltage + * pairs that the device will support per voltage domain. This is called + * Operating Points or OPP. The actual definitions of OMAP Operating Points + * varies over silicon within the same family of devices. For a specific + * domain, you can have a set of {frequency, voltage} pairs and this is denoted + * by an array of omap_opp_def. As the kernel boots and more information is + * available, a set of these are activated based on the precise nature of + * device the kernel boots up on. It is interesting to remember that each IP + * which belongs to a voltage domain may define their own set of OPPs on top + * of this - but this is handled by the appropriate driver. + */ +struct omap_opp_def { + bool enabled; + unsigned long freq; + u32 u_volt; +}; + +/* Initialization wrapper */ +#define OMAP_OPP_DEF(_enabled, _freq, _uv) \ +{ \ + .enabled = _enabled, \ + .freq = _freq, \ + .u_volt = _uv, \ +} + +/* Terminator for the initialization list */ +#define OMAP_OPP_DEF_TERMINATOR OMAP_OPP_DEF(0, 0, 0) + +/** + * opp_init_list() - Initialize an opp list from the opp definitions + * @opp_defs: Initial opp definitions to create the list. + * + * This function creates a list of opp definitions and returns a handle. + * This list can be used to further validation/search/modifications. New + * opp entries can be added to this list by using opp_add(). + * + * In the case of error, ERR_PTR is returned to the caller and should be + * appropriately handled with IS_ERR. + */ +struct omap_opp __init *opp_init_list(const struct omap_opp_def *opp_defs); + +/** + * opp_add() - Add an OPP table from a table definitions + * @oppl: List to add the OPP to + * @opp_def: omap_opp_def to describe the OPP which we want to add to list. + * + * This function adds an opp definition to the opp list and returns + * a handle representing the new OPP list. This handle is then used for further + * validation, search, modification operations on the OPP list. + * + * This function returns the pointer to the allocated list through oppl if + * success, else corresponding ERR_PTR value. Caller should NOT free the oppl. + * opps_defs can be freed after use. + * + * NOTE: caller should assume that on success, oppl is probably populated with + * a new handle and the new handle should be used for further referencing + */ +struct omap_opp *opp_add(struct omap_opp *oppl, + const struct omap_opp_def *opp_def); + +/** + * opp_enable() - Enable a specific OPP + * @opp: Pointer to opp + * + * Enables a provided opp. If the operation is valid, this returns 0, else the + * corresponding error value. + * + * OPP used here is from the the opp_is_valid/opp_has_freq or other search + * functions + */ +int opp_enable(struct omap_opp *opp); + +/** + * opp_disable() - Disable a specific OPP + * @opp: Pointer to opp + * + * Disables a provided opp. If the operation is valid, this returns 0, else the + * corresponding error value. + * + * OPP used here is from the the opp_is_valid/opp_has_freq or other search + * functions + */ +int opp_disable(struct omap_opp *opp); + +#endif /* __ASM_ARM_OMAP_OPP_H */ diff --git a/arch/arm/plat-omap/opp.c b/arch/arm/plat-omap/opp.c new file mode 100644 index 0000000..c4dc07b --- /dev/null +++ b/arch/arm/plat-omap/opp.c @@ -0,0 +1,271 @@ +/* + * OMAP OPP Interface + * + * Copyright (C) 2009 Texas Instruments Incorporated. + * Nishanth Menon + * + * 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 + +/* + * DEPRECATED: Meant to detect end of opp array + * This is meant to help co-exist with current SRF etc + * TODO: REMOVE! + */ +#define OPP_TERM(opp) (!(opp)->rate && !(opp)->vsel && !(opp)->enabled) + +/* + * DEPRECATED: Meant to convert vsel value to uVolt + * This is meant to help co-exist with current SRF etc + * TODO: REMOVE! + */ +static inline unsigned long vsel_to_uv(const u8 vsel) +{ + return (((vsel * 125) + 6000)) * 100; +} + +/* + * DEPRECATED: Meant to convert uVolt to vsel value + * This is meant to help co-exist with current SRF etc + * TODO: REMOVE! + */ +static inline unsigned char uv_to_vsel(unsigned long uV) +{ + return ((uV / 100) - 6000) / 125; +} + +unsigned long opp_get_voltage(const struct omap_opp *opp) +{ + if (unlikely(!opp || IS_ERR(opp)) || !opp->enabled) { + pr_err("%s: Invalid parameters being passed\n", __func__); + return 0; + } + return vsel_to_uv(opp->vsel); +} + +unsigned long opp_get_freq(const struct omap_opp *opp) +{ + if (unlikely(!opp || IS_ERR(opp)) || !opp->enabled) { + pr_err("%s: Invalid parameters being passed\n", __func__); + return 0; + } + return opp->rate; +} + +int opp_get_opp_count(const struct omap_opp *oppl) +{ + struct omap_opp *opp; + u8 n = 0; + + if (unlikely(!oppl || IS_ERR(oppl))) { + pr_err("%s: Invalid parameters being passed\n", __func__); + return -EINVAL; + } + opp = (struct omap_opp *)oppl; + opp++; /* skip initial terminator */ + while (!OPP_TERM(opp)) { + if (opp->enabled) + n++; + opp++; + } + return n; +} + +struct omap_opp *opp_find_freq_exact(struct omap_opp *oppl, + unsigned long freq, bool enabled) +{ + struct omap_opp *opp = (struct omap_opp *)oppl; + + if (unlikely(!oppl || IS_ERR(oppl))) { + pr_err("%s: Invalid parameters being passed\n", __func__); + return ERR_PTR(-EINVAL); + } + /* skip initial terminator */ + if (OPP_TERM(opp)) + opp++; + while (!OPP_TERM(opp)) { + if ((opp->rate == freq) && (opp->enabled == enabled)) + break; + opp++; + } + + return OPP_TERM(opp) ? ERR_PTR(-ENOENT) : opp; +} + +struct omap_opp *opp_find_freq_approx(struct omap_opp *oppl, + unsigned long *freq, u8 dir_flag) +{ + struct omap_opp *opp = (struct omap_opp *)oppl; + + if (unlikely(!oppl || IS_ERR(oppl) || !freq || IS_ERR(freq))) { + pr_err("%s: Invalid parameters being passed\n", __func__); + return ERR_PTR(-EINVAL); + } + /* skip initial terminator */ + if (OPP_TERM(opp)) { + opp++; + /* If searching init list for a high val, skip to very top */ + if (dir_flag == OPP_SEARCH_LOW) + while (!OPP_TERM(opp + 1)) + opp++; + } + while (!OPP_TERM(opp)) { + if (opp->enabled && + (((dir_flag == OPP_SEARCH_HIGH) && (opp->rate >= *freq)) || + ((dir_flag == OPP_SEARCH_LOW) && (opp->rate <= *freq)))) + break; + opp += (dir_flag == OPP_SEARCH_LOW) ? -1 : 1; + } + + if (OPP_TERM(opp)) + return ERR_PTR(-ENOENT); + + *freq = opp->rate; + return opp; +} + +/* wrapper to reuse converting opp_def to opp struct */ +static void omap_opp_populate(struct omap_opp *opp, + const struct omap_opp_def *opp_def) +{ + opp->rate = opp_def->freq; + opp->enabled = opp_def->enabled; + opp->vsel = uv_to_vsel(opp_def->u_volt); + /* round off to higher voltage */ + if (opp_def->u_volt > vsel_to_uv(opp->vsel)) + opp->vsel++; +} + +struct omap_opp *opp_add(struct omap_opp *oppl, + const struct omap_opp_def *opp_def) +{ + struct omap_opp *opp, *oppt, *oppr; + u8 n, i, ins; + + if (unlikely(!oppl || IS_ERR(oppl) || !opp_def || IS_ERR(opp_def))) { + pr_err("%s: Invalid params being passed\n", __func__); + return ERR_PTR(-EINVAL); + } + /* need a start terminator.. */ + if (unlikely(!OPP_TERM(oppl))) { + pr_err("%s: Expected a start terminator!!\n", __func__); + return ERR_PTR(-EINVAL); + } + n = 0; + opp = oppl; + opp++; + while (!OPP_TERM(opp)) { + n++; + opp++; + } + /* lets now reallocate memory */ + oppr = kmalloc(sizeof(struct omap_opp) * (n + 3), GFP_KERNEL); + if (!oppr) { + pr_err("%s: No memory for new opp array\n", __func__); + return ERR_PTR(-ENOMEM); + } + + /* Simple insertion sort */ + opp = oppl; + oppt = oppr; + ins = 0; + i = 0; + do { + if (ins || opp->rate < opp_def->freq) { + memcpy(oppt, opp, sizeof(struct omap_opp)); + opp++; + } else { + omap_opp_populate(oppt, opp_def); + ins++; + } + oppt->opp_id = i; + oppt++; + i++; + } while (!OPP_TERM(opp)); + + /* If nothing got inserted, this belongs to the end */ + if (!ins) { + omap_opp_populate(oppt, opp_def); + oppt->opp_id = i; + oppt++; + } + /* Put the terminator back on */ + memcpy(oppt, opp, sizeof(struct omap_opp)); + + /* Free the old list */ + kfree(oppl); + + return oppr; +} + +struct omap_opp __init *opp_init_list(const struct omap_opp_def *opp_defs) +{ + struct omap_opp_def *t = (struct omap_opp_def *)opp_defs; + struct omap_opp *opp, *oppl; + u8 n = 0, i = 1; + + if (unlikely(!opp_defs || IS_ERR(opp_defs))) { + pr_err("%s: Invalid params being passed\n", __func__); + return ERR_PTR(-EINVAL); + } + /* Grab a count */ + while (t->enabled || t->freq || t->u_volt) { + n++; + t++; + } + + oppl = kmalloc(sizeof(struct omap_opp) * (n + 2), GFP_KERNEL); + if (!oppl) { + pr_err("%s: No memory for opp array\n", __func__); + return ERR_PTR(-ENOMEM); + } + opp = oppl; + /* Setup start terminator - SRF depends on this for indexing :( */ + opp->rate = 0; + opp->enabled = 0; + opp->vsel = 0; + opp++; + while (n) { + omap_opp_populate(opp, opp_defs); + opp->opp_id = i; + n--; + opp++; + opp_defs++; + i++; + } + /* Setup terminator - this is for our search algos */ + opp->rate = 0; + opp->enabled = 0; + opp->vsel = 0; + return oppl; +} + +int opp_enable(struct omap_opp *opp) +{ + if (unlikely(!opp || IS_ERR(opp))) { + pr_err("%s: Invalid parameters being passed\n", __func__); + return -EINVAL; + } + opp->enabled = true; + return 0; +} + +int opp_disable(struct omap_opp *opp) +{ + if (unlikely(!opp || IS_ERR(opp))) { + pr_err("%s: Invalid parameters being passed\n", __func__); + return -EINVAL; + } + opp->enabled = false; + return 0; +}