diff mbox

[6/7] ARM: omap: abb: init & transition functions

Message ID 1349313974-5473-7-git-send-email-mturquette@ti.com (mailing list archive)
State New, archived
Headers show

Commit Message

Mike Turquette Oct. 4, 2012, 1:26 a.m. UTC
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@ti.com>
Signed-off-by: Mike Turquette <mturquette@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

Comments

Dimitar Dimitrov Oct. 4, 2012, 2:26 a.m. UTC | #1
On Wed, Oct 3, 2012 at 6:26 PM, Mike Turquette <mturquette@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@ti.com>
> Signed-off-by: Mike Turquette <mturquette@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@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@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

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@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);
+	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