/* * OMAP3/4 LDO users core * * Copyright (C) 2011 Texas Instruments Incorporated - http://www.ti.com/ * Mike Turquette * 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 #include #include #include #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/SLOW) * @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; /* Select proper Adaptive Body-Bias(ABB) */ switch (abb_type) { case OMAP_ABB_NOMINAL_OPP: /* setup to bypass */ voltdm->rmw(abb->setup_bits->active_fbb_mask | abb->setup_bits->active_rbb_mask, 0x0, abb->setup_reg); break; case OMAP_ABB_SLOW_OPP: /* setup to RBB */ voltdm->rmw(abb->setup_bits->active_fbb_mask | abb->setup_bits->active_rbb_mask, abb->setup_bits->active_rbb_mask, abb->setup_reg); break; case OMAP_ABB_FAST_OPP: /* setup to FBB */ voltdm->rmw(abb->setup_bits->active_fbb_mask | abb->setup_bits->active_rbb_mask, abb->setup_bits->active_fbb_mask, abb->setup_reg); break; case OMAP_ABB_NONE: /* Fall through */ default: /* Should have never been here! */ WARN_ONCE(1, "%s: voltage domain %s: abb type %d!!!\n", __func__, voltdm->name, abb_type); return -EINVAL; } /* 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; } /* Prescale - override and always go bypass */ if (is_prescale) { /* if we are already in bypass, dont need to do it again */ if (curr_abb == OMAP_ABB_NOMINAL_OPP) goto out; target_abb = OMAP_ABB_NOMINAL_OPP; } /* Use RBB ONLY if calibrated voltage is achieved */ if (!is_prescale && target_abb == OMAP_ABB_SLOW_OPP && !target_vdata->volt_calibrated) { /* Skip setting up RBB at this point of transition */ goto out; } 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); /* 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; }