diff options
Diffstat (limited to 'arch/arm/mach-omap2/ldo.c')
-rw-r--r-- | arch/arm/mach-omap2/ldo.c | 333 |
1 files changed, 333 insertions, 0 deletions
diff --git a/arch/arm/mach-omap2/ldo.c b/arch/arm/mach-omap2/ldo.c new file mode 100644 index 0000000..13ee2a3 --- /dev/null +++ b/arch/arm/mach-omap2/ldo.c @@ -0,0 +1,333 @@ +/* + * OMAP3/4 LDO users core + * + * Copyright (C) 2011 Texas Instruments Incorporated - http://www.ti.com/ + * Mike Turquette <mturquette@ti.com> + * 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 <linux/kernel.h> +#include <linux/delay.h> +#include <linux/init.h> + +#include <plat/cpu.h> +#include "voltage.h" +#include "ldo.h" + +/** + * _is_abb_enabled() - check if abb is enabled + * @voltdm: voltage domain to check for + * @abb: abb instance pointer + * + * Returns true if enabled, else returns false + */ +static inline bool _is_abb_enabled(struct voltagedomain *voltdm, + struct omap_ldo_abb_instance *abb) +{ + return (voltdm->read(abb->setup_reg) & abb->setup_bits->enable_mask) ? + true : false; +} + +/** + * _abb_set_availability() - sets the availability of the ABB LDO + * @voltdm: voltage domain for which we would like to set + * @abb: abb instance pointer + * @available: should I enable/disable the LDO? + * + * Depending on the request, it enables/disables the LDO if it was not + * in that state already. + */ +static inline void _abb_set_availability(struct voltagedomain *voltdm, + struct omap_ldo_abb_instance *abb, + bool available) +{ + if (_is_abb_enabled(voltdm, abb) == available) + return; + + voltdm->rmw(abb->setup_bits->enable_mask, + (available) ? abb->setup_bits->enable_mask : 0, + abb->setup_reg); +} + +/** + * _abb_wait_tranx() - wait for abb tranxdone event + * @voltdm: voltage domain we are operating on + * @abb: pointer to the abb instance + * + * Returns -ETIMEDOUT if the event is not set on time. + */ +static int _abb_wait_tranx(struct voltagedomain *voltdm, + struct omap_ldo_abb_instance *abb) +{ + int timeout; + int ret; + + timeout = 0; + while (timeout++ < abb->tranx_timeout) { + ret = abb->ops->check_txdone(abb->prm_irq_id); + if (ret) + break; + + udelay(1); + } + + if (timeout >= abb->tranx_timeout) { + pr_warning("%s:%s: ABB TRANXDONE waittimeout(timeout=%d)\n", + __func__, voltdm->name, timeout); + return -ETIMEDOUT; + } + return 0; +} + +/** + * _abb_clear_tranx() - clear abb tranxdone event + * @voltdm: voltage domain we are operating on + * @abb: pointer to the abb instance + * + * Returns -ETIMEDOUT if the event is not cleared on time. + */ +static int _abb_clear_tranx(struct voltagedomain *voltdm, + struct omap_ldo_abb_instance *abb) +{ + int timeout; + int ret; + + /* clear interrupt status */ + timeout = 0; + while (timeout++ < abb->tranx_timeout) { + abb->ops->clear_txdone(abb->prm_irq_id); + + ret = abb->ops->check_txdone(abb->prm_irq_id); + if (!ret) + break; + + udelay(1); + } + + if (timeout >= abb->tranx_timeout) { + pr_warning("%s:%s: ABB TRANXDONE timeout(timeout=%d)\n", + __func__, voltdm->name, timeout); + return -ETIMEDOUT; + } + return 0; +} + +/** + * _abb_set_abb() - helper to actually set ABB (NOMINAL/FAST) + * @voltdm: voltage domain we are operating on + * @abb_type: ABB type we want to set + */ +static int _abb_set_abb(struct voltagedomain *voltdm, int abb_type) +{ + struct omap_ldo_abb_instance *abb = voltdm->abb; + int ret; + + ret = _abb_clear_tranx(voltdm, abb); + if (ret) + return ret; + + /* program next state of ABB ldo */ + voltdm->rmw(abb->ctrl_bits->opp_sel_mask, + abb_type << __ffs(abb->ctrl_bits->opp_sel_mask), + abb->ctrl_reg); + + /* initiate ABB ldo change */ + voltdm->rmw(abb->ctrl_bits->opp_change_mask, + abb->ctrl_bits->opp_change_mask, abb->ctrl_reg); + + /* Wait for conversion completion */ + ret = _abb_wait_tranx(voltdm, abb); + WARN_ONCE(ret, "%s: voltdm %s ABB TRANXDONE was not set on time:%d\n", + __func__, voltdm->name, ret); + /* clear interrupt status */ + ret |= _abb_clear_tranx(voltdm, abb); + + return ret; +} + +/** + * _abb_scale() - wrapper which does the necessary things for pre and post scale + * @voltdm: voltage domain to operate on + * @target_volt: voltage we are going to + * @is_prescale: are we doing a prescale operation? + * + * NOTE: We expect caller ensures that a specific voltdm is modified + * sequentially. All locking is expected to be implemented by users + * of LDO functions + */ +static int _abb_scale(struct voltagedomain *voltdm, + struct omap_volt_data *target_vdata, bool is_prescale) +{ + int ret = 0; + int curr_abb, target_abb; + struct omap_ldo_abb_instance *abb; + + if (IS_ERR_OR_NULL(target_vdata)) { + pr_err("%s:%s: Invalid volt data tv=%p!\n", __func__, + voltdm->name, target_vdata); + return -EINVAL; + } + + abb = voltdm->abb; + if (IS_ERR_OR_NULL(abb)) { + WARN(1, "%s:%s: no abb structure!\n", __func__, voltdm->name); + return -EINVAL; + } + + curr_abb = abb->__cur_abb_type; + target_abb = target_vdata->abb_type; + + pr_debug("%s: %s: Enter: t_v=%ld scale=%d c_abb=%d t_abb=%d ret=%d\n", + __func__, voltdm->name, omap_get_nominal_voltage(target_vdata), + is_prescale, curr_abb, target_abb, ret); + + /* If we were'nt booting and there is no change, we get out */ + if (target_abb == curr_abb && voltdm->curr_volt) + goto out; + + /* Do we have an invalid ABB entry? scream for a fix! */ + if (curr_abb == OMAP_ABB_NONE || target_abb == OMAP_ABB_NONE) { + WARN(1, "%s:%s: INVALID abb entries? curr=%d target=%d\n", + __func__, voltdm->name, curr_abb, target_abb); + return -EINVAL; + } + + /* + * We set up ABB as follows: + * if we are scaling *to* a voltage which needs ABB, do it in post + * if we are scaling *from* a voltage which needs ABB, do it in pre + * So, if the conditions are in reverse, we just return happy + */ + if (is_prescale && (target_abb > curr_abb)) + goto out; + + if (!is_prescale && (target_abb < curr_abb)) + goto out; + + /* Time to set ABB now */ + ret = _abb_set_abb(voltdm, target_abb); + if (!ret) { + abb->__cur_abb_type = target_abb; + pr_debug("%s: %s: scaled - t_abb=%d!\n", __func__, + voltdm->name, target_abb); + } else { + pr_warning("%s: %s: failed scale: t_abb=%d (%d)!\n", __func__, + voltdm->name, target_abb, ret); + } + +out: + pr_debug("%s: %s:Exit: t_v=%ld scale=%d c_abb=%d t_abb=%d ret=%d\n", + __func__, voltdm->name, omap_get_nominal_voltage(target_vdata), + is_prescale, curr_abb, target_abb, ret); + return ret; + +} + +/** + * omap_ldo_abb_pre_scale() - Enable required ABB strategy before voltage scale + * @voltdm: voltage domain to operate on + * @target_volt: target voltage data we moved to. + */ +int omap_ldo_abb_pre_scale(struct voltagedomain *voltdm, + struct omap_volt_data *target_vdata) +{ + return _abb_scale(voltdm, target_vdata, true); +} + +/** + * omap_ldo_abb_pre_scale() - Enable required ABB strategy after voltage scale + * @voltdm: voltage domain operated on + * @target_volt: target voltage we are going to + */ +int omap_ldo_abb_post_scale(struct voltagedomain *voltdm, + struct omap_volt_data *target_vdata) +{ + return _abb_scale(voltdm, target_vdata, false); +} + +/** + * omap_ldo_abb_init() - initialize the ABB LDO for associated for this domain + * @voltdm: voltdm for which we need to initialize the ABB LDO + * + * Programs up the the configurations that dont change in the domain + * + * Return 0 if all goes fine, else returns appropriate error value + */ +void __init omap_ldo_abb_init(struct voltagedomain *voltdm) +{ + u32 sys_clk_rate; + u32 cycle_rate; + u32 settling_time; + u32 wait_count_val; + struct omap_ldo_abb_instance *abb; + + if (IS_ERR_OR_NULL(voltdm)) { + pr_err("%s: No voltdm?\n", __func__); + return; + } + if (!voltdm->read || !voltdm->write || !voltdm->rmw) { + pr_err("%s: No read/write/rmw API for accessing vdd_%s regs\n", + __func__, voltdm->name); + return; + } + + abb = voltdm->abb; + if (IS_ERR_OR_NULL(abb)) + return; + if (IS_ERR_OR_NULL(abb->ctrl_bits) || IS_ERR_OR_NULL(abb->setup_bits)) { + pr_err("%s: Corrupted ABB configuration on vdd_%s regs\n", + __func__, voltdm->name); + return; + } + + /* + * SR2_WTCNT_VALUE must be programmed with the expected settling time + * for ABB ldo transition. This value depends on the cycle rate for + * the ABB IP (varies per OMAP family), and the system clock frequency + * (varies per board). The formula is: + * + * SR2_WTCNT_VALUE = SettlingTime / (CycleRate / SystemClkRate)) + * where SettlingTime is in micro-seconds and SystemClkRate is in MHz. + * + * To avoid dividing by zero multiply both CycleRate and SettlingTime + * 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); + cycle_rate = abb->cycle_rate * 10; + settling_time = abb->settling_time * 10; + + /* Calculate cycle rate */ + cycle_rate = DIV_ROUND_CLOSEST(cycle_rate, sys_clk_rate); + + /* Calulate SR2_WTCNT_VALUE */ + wait_count_val = DIV_ROUND_CLOSEST(settling_time, cycle_rate); + + voltdm->rmw(abb->setup_bits->wait_count_mask, + wait_count_val << __ffs(abb->setup_bits->wait_count_mask), + abb->setup_reg); + + /* Allow Forward Body-Bias */ + voltdm->rmw(abb->setup_bits->active_fbb_mask, + abb->setup_bits->active_fbb_mask, abb->setup_reg); + + /* Enable ABB */ + _abb_set_availability(voltdm, abb, true); + + /* + * Beware of the bootloader! + * Initialize current abb type based on what we read off the reg. + * we cant trust the initial state based off boot voltage's volt_data + * even. Not all bootloaders are nice :( + */ + abb->__cur_abb_type = (voltdm->read(abb->ctrl_reg) & + abb->ctrl_bits->opp_sel_mask) >> + __ffs(abb->ctrl_bits->opp_sel_mask); + + return; +} |