On Wed, Oct 3, 2012 at 6:26 PM, Mike Turquette <mturquette@xxxxxx> 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@xxxxxx> > Signed-off-by: Mike Turquette <mturquette@xxxxxxxxxx> > --- > 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@xxxxxx> > + * > + * 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@xxxxxxxxxxxxxxx > More majordomo info at http://vger.kernel.org/majordomo-info.html -- To unsubscribe from this list: send the line "unsubscribe linux-omap" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html