/* Voltage scale and accessory APIs */ static int _pre_volt_scale(struct omap_vdd_info *vdd, unsigned long target_volt, u8 *target_vsel, u8 *current_vsel) { struct omap_volt_data *volt_data; const struct omap_vc_common_data *vc_common; const struct omap_vp_common_data *vp_common; u32 vc_cmdval, vp_errgain_val; vc_common = vdd->vc_data->vc_common; vp_common = vdd->vp_data->vp_common; /* Check if suffiecient pmic info is available for this vdd */ if (!vdd->pmic_info) { pr_err("%s: Insufficient pmic info to scale the vdd_%s\n", __func__, vdd->voltdm.name); return -EINVAL; } if (!vdd->pmic_info->uv_to_vsel) { pr_err("%s: PMIC function to convert voltage in uV to" "vsel not registered. Hence unable to scale voltage" "for vdd_%s\n", __func__, vdd->voltdm.name); return -ENODATA; } if (!vdd->read_reg || !vdd->write_reg) { pr_err("%s: No read/write API for accessing vdd_%s regs\n", __func__, vdd->voltdm.name); return -EINVAL; } /* Get volt_data corresponding to target_volt */ volt_data = omap_voltage_get_voltdata(&vdd->voltdm, target_volt); if (IS_ERR(volt_data)) volt_data = NULL; *target_vsel = vdd->pmic_info->uv_to_vsel(target_volt); *current_vsel = vdd->read_reg(prm_mod_offs, vdd->vp_data->voltage); /* Setting the ON voltage to the new target voltage */ vc_cmdval = vdd->read_reg(prm_mod_offs, vdd->vc_data->cmdval_reg); vc_cmdval &= ~vc_common->cmd_on_mask; vc_cmdval |= (*target_vsel << vc_common->cmd_on_shift); vdd->write_reg(vc_cmdval, prm_mod_offs, vdd->vc_data->cmdval_reg); /* Setting vp errorgain based on the voltage */ if (volt_data) { vp_errgain_val = vdd->read_reg(prm_mod_offs, vdd->vp_data->vpconfig); vdd->vp_rt_data.vpconfig_errorgain = volt_data->vp_errgain; vp_errgain_val &= ~vp_common->vpconfig_errorgain_mask; vp_errgain_val |= vdd->vp_rt_data.vpconfig_errorgain << vp_common->vpconfig_errorgain_shift; vdd->write_reg(vp_errgain_val, prm_mod_offs, vdd->vp_data->vpconfig); } return 0; }
int omap_vp_update_errorgain(struct voltagedomain *voltdm, unsigned long target_volt) { struct omap_volt_data *volt_data; if (!voltdm->vp) return -EINVAL; /* Get volt_data corresponding to target_volt */ volt_data = omap_voltage_get_voltdata(voltdm, target_volt); if (IS_ERR(volt_data)) return -EINVAL; /* Setting vp errorgain based on the voltage */ voltdm->rmw(voltdm->vp->common->vpconfig_errorgain_mask, volt_data->vp_errgain << __ffs(voltdm->vp->common->vpconfig_errorgain_mask), voltdm->vp->vpconfig); return 0; }
/** * 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; u32 reg, trim, rbb; struct omap_ldo_abb_instance *abb; struct omap_volt_data *volt_data; 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); /* * Determine MPU ABB state at OPP_TURBO on 4460 * * On 4460 all OPPs have preset states for the MPU's ABB LDO, except * for OPP_TURBO. OPP_TURBO may require bypass, FBB or RBB depending * on a combination of characterisation data blown into eFuse register * CONTROL_STD_FUSE_OPP_DPLL_1. * * Bits 18 & 19 of that register signify DPLL_MPU trim (see * arch/arm/mach-omap2/omap4-trim-quirks.c). OPP_TURBO might put MPU's * ABB LDO into bypass or FBB based on this value. * * Bit 20 siginifies if RBB should be enabled. If set it will always * override the values from bits 18 & 19. * * The table below captures the valid combinations: * * Bit 18|Bit 19|Bit 20|ABB type * 0 0 0 bypass * 0 1 0 bypass (invalid combo) * 1 0 0 FBB (2.4GHz DPLL_MPU) * 1 1 0 FBB (3GHz DPLL_MPU) * 0 0 1 RBB * 0 1 1 RBB (invalid combo) * 1 0 1 RBB (2.4GHz DPLL_MPU) * 1 1 1 RBB (3GHz DPLL_MPU) */ if (cpu_is_omap446x() && !strcmp("mpu", voltdm->name)) { /* read eFuse register here */ reg = omap_ctrl_readl(OMAP4_CTRL_MODULE_CORE_STD_FUSE_OPP_DPLL_1); trim = reg & OMAP4460_MPU_OPP_DPLL_TRIM; rbb = reg & OMAP4460_MPU_OPP_DPLL_TURBO_RBB; volt_data = omap_voltage_get_voltdata(voltdm, OMAP4460_VDD_MPU_OPPTURBO_UV); /* OPP_TURBO is FAST_OPP (FBB) by default */ if (rbb) volt_data->abb_type = OMAP_ABB_SLOW_OPP; else if (!trim) volt_data->abb_type = OMAP_ABB_NOMINAL_OPP; } /* 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; }