blob: 13ee2a3f8d0a326df38ae9bce6c95e24e22bbfff [file] [log] [blame] [edit]
/*
* OMAP3/4 LDO users core
*
* Copyright (C) 2011 Texas Instruments Incorporated - http://www.ti.com/
* Mike Turquette <[email protected]>
* 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;
}