From patchwork Thu Oct 4 01:26:13 2012 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Mike Turquette X-Patchwork-Id: 1544381 Return-Path: X-Original-To: patchwork-linux-arm@patchwork.kernel.org Delivered-To: patchwork-process-083081@patchwork1.kernel.org Received: from merlin.infradead.org (merlin.infradead.org [205.233.59.134]) by patchwork1.kernel.org (Postfix) with ESMTP id B9A523FD9C for ; Thu, 4 Oct 2012 01:29:52 +0000 (UTC) Received: from localhost ([::1] helo=merlin.infradead.org) by merlin.infradead.org with esmtp (Exim 4.76 #1 (Red Hat Linux)) id 1TJaEF-0006iJ-Tu; Thu, 04 Oct 2012 01:27:57 +0000 Received: from devils.ext.ti.com ([198.47.26.153]) by merlin.infradead.org with esmtps (Exim 4.76 #1 (Red Hat Linux)) id 1TJaD0-0006GB-4o for linux-arm-kernel@lists.infradead.org; Thu, 04 Oct 2012 01:26:46 +0000 Received: from dlelxv30.itg.ti.com ([172.17.2.17]) by devils.ext.ti.com (8.13.7/8.13.7) with ESMTP id q941Qacw002273; Wed, 3 Oct 2012 20:26:36 -0500 Received: from DLEE74.ent.ti.com (dlee74.ent.ti.com [157.170.170.8]) by dlelxv30.itg.ti.com (8.13.8/8.13.8) with ESMTP id q941QaQl024945; Wed, 3 Oct 2012 20:26:36 -0500 Received: from dlelxv22.itg.ti.com (172.17.1.197) by DLEE74.ent.ti.com (157.170.170.8) with Microsoft SMTP Server id 14.1.323.3; Wed, 3 Oct 2012 20:26:36 -0500 Received: from nucleus.nsc.com (nucleus.nsc.com [10.188.36.112]) by dlelxv22.itg.ti.com (8.13.8/8.13.8) with ESMTP id q941QWfZ015948; Wed, 3 Oct 2012 20:26:35 -0500 From: Mike Turquette To: , Subject: [PATCH 6/7] ARM: omap: abb: init & transition functions Date: Wed, 3 Oct 2012 18:26:13 -0700 Message-ID: <1349313974-5473-7-git-send-email-mturquette@ti.com> X-Mailer: git-send-email 1.7.9.5 In-Reply-To: <1349313974-5473-1-git-send-email-mturquette@ti.com> References: <1349313974-5473-1-git-send-email-mturquette@ti.com> MIME-Version: 1.0 X-Spam-Note: CRM114 invocation failed X-Spam-Score: -9.0 (---------) X-Spam-Report: SpamAssassin version 3.3.2 on merlin.infradead.org summary: Content analysis details: (-9.0 points) pts rule name description ---- ---------------------- -------------------------------------------------- -5.0 RCVD_IN_DNSWL_HI RBL: Sender listed at http://www.dnswl.org/, high trust [198.47.26.153 listed in list.dnswl.org] -0.0 SPF_PASS SPF: sender matches SPF record -2.1 RP_MATCHES_RCVD Envelope sender domain matches handover relay domain -1.9 BAYES_00 BODY: Bayes spam probability is 0 to 1% [score: 0.0000] Cc: linux-omap@vger.kernel.org, Mike Turquette , linux-arm-kernel@lists.infradead.org, Mike Turquette X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: linux-arm-kernel-bounces@lists.infradead.org Errors-To: linux-arm-kernel-bounces+patchwork-linux-arm=patchwork.kernel.org@lists.infradead.org The Adaptive Body-Bias ldo can be set to bypass, Forward Body-Bias or Reverse Body-Bias during a voltage transition. The ABB programming sequence depends on whether voltage is scaling up or down. This patch implements the Adaptive Body-Bias ldo initialization routine and the transition sequence which is needed after any voltage scaling operation. Note that this sequence will need to be revisited someday when the various SmartReflex AVS features, fixes and improvements are upstreamed and enabled for OMAP2+ kernels. Signed-off-by: Mike Turquette Signed-off-by: Mike Turquette --- arch/arm/mach-omap2/Makefile | 2 +- arch/arm/mach-omap2/abb.c | 322 ++++++++++++++++++++++++++++++++++++++++++ arch/arm/mach-omap2/abb.h | 9 ++ 3 files changed, 332 insertions(+), 1 deletion(-) create mode 100644 arch/arm/mach-omap2/abb.c diff --git a/arch/arm/mach-omap2/Makefile b/arch/arm/mach-omap2/Makefile index 57e053e..a262aaa 100644 --- a/arch/arm/mach-omap2/Makefile +++ b/arch/arm/mach-omap2/Makefile @@ -107,7 +107,7 @@ obj-$(CONFIG_ARCH_OMAP4) += $(omap-prcm-4-5-common) prm44xx.o obj-$(CONFIG_SOC_OMAP5) += $(omap-prcm-4-5-common) # OMAP voltage domains -voltagedomain-common := voltage.o vc.o vp.o +voltagedomain-common := voltage.o vc.o vp.o abb.o obj-$(CONFIG_ARCH_OMAP2) += $(voltagedomain-common) obj-$(CONFIG_ARCH_OMAP2) += voltagedomains2xxx_data.o obj-$(CONFIG_ARCH_OMAP3) += $(voltagedomain-common) diff --git a/arch/arm/mach-omap2/abb.c b/arch/arm/mach-omap2/abb.c new file mode 100644 index 0000000..e8a3ae0 --- /dev/null +++ b/arch/arm/mach-omap2/abb.c @@ -0,0 +1,322 @@ +/* + * OMAP Adaptive Body-Bias core + * + * Copyright (C) 2011 Texas Instruments, Inc. + * Mike Turquette + * + * 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 "abb.h" +#include "voltage.h" + +/** + * omap_abb_set_opp - program ABB ldo based on new voltage + * + * @voltdm - voltage domain that just finished scaling voltage + * @opp_sel - target ABB ldo operating mode + * + * Program the ABB ldo to the new state (if necessary), clearing the + * PRM_IRQSTATUS bit before and after the transition. Returns 0 on + * success, -ETIMEDOUT otherwise. + */ +int omap_abb_set_opp(struct voltagedomain *voltdm, u8 opp_sel) +{ + struct omap_abb_instance *abb = voltdm->abb; + int ret, timeout; + + /* bail early if no transition is necessary */ + if (opp_sel == abb->_opp_sel) + return 0; + + /* clear interrupt status */ + timeout = 0; + while (timeout++ < ABB_TRANXDONE_TIMEOUT) { + abb->common->ops->clear_tranxdone(abb->prm_irq_id); + + ret = abb->common->ops->check_tranxdone(abb->prm_irq_id); + if (!ret) + break; + + udelay(1); + } + + if (timeout >= ABB_TRANXDONE_TIMEOUT) { + pr_warn("%s: vdd_%s ABB TRANXDONE timeout\n", + __func__, voltdm->name); + return -ETIMEDOUT; + } + + /* program the setup register */ + switch (opp_sel) { + case OMAP_ABB_NOMINAL_OPP: + voltdm->rmw(abb->common->active_fbb_sel_mask, + 0x0, + abb->setup_offs); + break; + case OMAP_ABB_FAST_OPP: + voltdm->rmw(abb->common->active_fbb_sel_mask, + abb->common->active_fbb_sel_mask, + abb->setup_offs); + break; + } + + /* program next state of ABB ldo */ + voltdm->rmw(abb->common->opp_sel_mask, + opp_sel << __ffs(abb->common->opp_sel_mask), + abb->ctrl_offs); + + /* initiate ABB ldo change */ + voltdm->rmw(abb->common->opp_change_mask, + abb->common->opp_change_mask, + abb->ctrl_offs); + + /* clear interrupt status */ + timeout = 0; + while (timeout++ < ABB_TRANXDONE_TIMEOUT) { + abb->common->ops->clear_tranxdone(abb->prm_irq_id); + + ret = abb->common->ops->check_tranxdone(abb->prm_irq_id); + if (!ret) + break; + + udelay(1); + } + + if (timeout >= ABB_TRANXDONE_TIMEOUT) { + pr_warn("%s: vdd_%s ABB TRANXDONE timeout\n", + __func__, voltdm->name); + return -ETIMEDOUT; + } + + /* track internal state */ + abb->_opp_sel = opp_sel; + + return 0; +} + +/** + * omap_abb_pre_scale - ABB transition pre-voltage scale, if needed + * + * @voltdm - voltage domain that is about to scale + * @target_volt - voltage that voltdm is scaling towards + * + * Changes the ABB ldo mode prior to scaling the voltage domain. + * Returns 0 on success, otherwise an error code. + */ +int omap_abb_pre_scale(struct voltagedomain *voltdm, + unsigned long target_volt) +{ + struct omap_abb_instance *abb = voltdm->abb; + struct omap_volt_data *cur_volt_data; + struct omap_volt_data *target_volt_data; + u8 opp_sel; + + /* sanity */ + if (!voltdm) + return -EINVAL; + + if (!abb) + return 0; + + /* + * XXX boot-time corner case: voltdm->nominal volt might be zero + * + * This implies that we're running at the default PMIC voltage, + * since voltdm->nominal_volt should have been populated in + * omap_voltage_late_init if the voltage had been scaled + * previously. The best way to fix this is for DT data to pass + * in PMIC boot voltage. + * + * For now, handle this by returning success (0) to not block + * the rest of the transition. + */ + if (!voltdm->nominal_volt) + return 0; + + cur_volt_data = omap_voltage_get_voltdata(voltdm, voltdm->nominal_volt); + target_volt_data = omap_voltage_get_voltdata(voltdm, target_volt); + + if (IS_ERR(cur_volt_data)) + return PTR_ERR(cur_volt_data); + + if (IS_ERR(target_volt_data)) + return PTR_ERR(target_volt_data); + + /* bail if the sequence is wrong */ + if (target_volt_data->volt_nominal > cur_volt_data->volt_nominal) + return 0; + + opp_sel = target_volt_data->opp_sel; + + /* bail early if no transition is necessary */ + if (opp_sel == abb->_opp_sel) + return 0; + + return omap_abb_set_opp(voltdm, opp_sel); +} + +/** + * omap_abb_post_scale - ABB transition post-voltage scale, if needed + * @voltdm - voltage domain that just finished scaling + * @target_volt - voltage that voltdm is scaling towards + * + * Changes the ABB ldo mode prior to scaling the voltage domain. + * Returns 0 on success, otherwise an error code. + */ +int omap_abb_post_scale(struct voltagedomain *voltdm, + unsigned long target_volt) +{ + struct omap_abb_instance *abb = voltdm->abb; + struct omap_volt_data *cur_volt_data; + struct omap_volt_data *target_volt_data; + u8 opp_sel; + + /* sanity */ + if (!voltdm) + return -EINVAL; + + if (!abb) + return 0; + + cur_volt_data = omap_voltage_get_voltdata(voltdm, voltdm->nominal_volt); + if (IS_ERR(cur_volt_data)) + return PTR_ERR(cur_volt_data); + + target_volt_data = omap_voltage_get_voltdata(voltdm, target_volt); + if (IS_ERR(target_volt_data)) + return PTR_ERR(target_volt_data); + + /* bail if the sequence is wrong */ + if (target_volt_data->volt_nominal < cur_volt_data->volt_nominal) + return 0; + + opp_sel = target_volt_data->opp_sel; + + /* bail early if no transition is necessary */ + if (opp_sel == abb->_opp_sel) + return 0; + + return omap_abb_set_opp(voltdm, opp_sel); +} + +/* + * omap_abb_enable - enable ABB ldo on a particular voltage domain + * + * @voltdm - pointer to particular voltage domain + */ +void omap_abb_enable(struct voltagedomain *voltdm) +{ + struct omap_abb_instance *abb = voltdm->abb; + + if (abb->enabled) + return; + + abb->enabled = true; + + voltdm->rmw(abb->common->sr2en_mask, abb->common->sr2en_mask, + abb->setup_offs); +} + +/* + * omap_abb_disable - disable ABB ldo on a particular voltage domain + * + * @voltdm - pointer to particular voltage domain + * + * Included for completeness. Not currently used but will be needed in the + * future if ABB is converted to a loadable module. + */ +void omap_abb_disable(struct voltagedomain *voltdm) +{ + struct omap_abb_instance *abb = voltdm->abb; + + if (!abb->enabled) + return; + + abb->enabled = false; + + voltdm->rmw(abb->common->sr2en_mask, + (0 << __ffs(abb->common->sr2en_mask)), + abb->setup_offs); +} + +/* + * omap_abb_init - Initialize an ABB ldo instance + * + * @voltdm: voltage domain upon which ABB ldo resides + * + * Initializes an individual ABB ldo for Forward Body-Bias. FBB is used to + * insure stability at higher voltages. Note that some older OMAP chips have a + * Reverse Body-Bias mode meant to save power at low voltage, but that mode is + * unsupported and phased out on newer chips. + */ +void __init omap_abb_init(struct voltagedomain *voltdm) +{ + struct omap_abb_instance *abb = voltdm->abb; + u32 sys_clk_rate; + u32 sr2_wt_cnt_val; + u32 clock_cycles; + u32 settling_time; + u32 val; + + if (IS_ERR_OR_NULL(abb)) + return; + + /* + * SR2_WTCNT_VALUE is the settling time for the ABB ldo after a + * transition and must be programmed with the correct time at boot. + * The value programmed into the register is the number of SYS_CLK + * clock cycles that match a given wall time profiled for the ldo. + * This value depends on: + * settling time of ldo in micro-seconds (varies per OMAP family) + * # of clock cycles per SYS_CLK period (varies per OMAP family) + * the SYS_CLK frequency in MHz (varies per board) + * The formula is: + * + * ldo settling time (in micro-seconds) + * SR2_WTCNT_VALUE = ------------------------------------------ + * (# system clock cycles) * (sys_clk period) + * + * Put another way: + * + * SR2_WTCNT_VALUE = settling time / (# SYS_CLK cycles / SYS_CLK rate)) + * + * To avoid dividing by zero multiply both "# clock cycles" and + * "settling time" by 10 such that the final result is the one we want. + */ + + /* convert SYS_CLK rate to MHz & prevent divide by zero */ + sys_clk_rate = DIV_ROUND_CLOSEST(voltdm->sys_clk.rate, 1000000); + clock_cycles = abb->common->clock_cycles * 10; + settling_time = abb->common->settling_time * 10; + + /* calculate cycle rate */ + clock_cycles = DIV_ROUND_CLOSEST(clock_cycles, sys_clk_rate); + + /* calulate SR2_WTCNT_VALUE */ + sr2_wt_cnt_val = DIV_ROUND_CLOSEST(settling_time, clock_cycles); + + voltdm->rmw(abb->common->sr2_wtcnt_value_mask, + (sr2_wt_cnt_val << __ffs(abb->common->sr2_wtcnt_value_mask)), + abb->setup_offs); + + /* did bootloader set OPP_SEL? */ + val = voltdm->read(abb->ctrl_offs); + val &= abb->common->opp_sel_mask; + abb->_opp_sel = val >> __ffs(abb->common->opp_sel_mask); + + /* enable the ldo if not done by bootloader */ + val = voltdm->read(abb->setup_offs); + val &= abb->common->sr2en_mask; + if (val) + abb->enabled = true; + else + omap_abb_enable(voltdm); + + return; +} diff --git a/arch/arm/mach-omap2/abb.h b/arch/arm/mach-omap2/abb.h index 2acc187..b18305f 100644 --- a/arch/arm/mach-omap2/abb.h +++ b/arch/arm/mach-omap2/abb.h @@ -82,4 +82,13 @@ extern struct omap_abb_instance omap36xx_abb_mpu; extern struct omap_abb_instance omap4_abb_mpu; extern struct omap_abb_instance omap4_abb_iva; +void omap_abb_init(struct voltagedomain *voltdm); +void omap_abb_enable(struct voltagedomain *voltdm); +void omap_abb_disble(struct voltagedomain *voltdm); +int omap_abb_set_opp(struct voltagedomain *voltdm, u8 opp_sel); +int omap_abb_pre_scale(struct voltagedomain *voltdm, + unsigned long target_volt); +int omap_abb_post_scale(struct voltagedomain *voltdm, + unsigned long target_volt); + #endif