[PATCH 6/7] ARM: omap: abb: init & transition functions
Dimitar Dimitrov
dinuxbg at gmail.com
Wed Oct 3 22:26:31 EDT 2012
On Wed, Oct 3, 2012 at 6:26 PM, Mike Turquette <mturquette at ti.com> wrote:
> 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 <mturquette at ti.com>
> Signed-off-by: Mike Turquette <mturquette at linaro.org>
> ---
> 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 <mturquette at ti.com>
> + *
> + * 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 <linux/init.h>
> +#include <linux/delay.h>
> +
> +#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);
There is a possible small rounding error here. Depending on
SYS_CLK_rate value, you might loose significant digits when converting
from Hz to MHz.
Also, wouldn't it be safer to do all multiplications first and then
divisions, like:
SR2_WTCNT_VALUE = ((settling time * SYS_CLK_rate_kHz) / # SYS_CLK
cycles ) / 1000
> + 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
> --
> 1.7.9.5
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-omap" in
> the body of a message to majordomo at vger.kernel.org
> More majordomo info at http://vger.kernel.org/majordomo-info.html
More information about the linux-arm-kernel
mailing list