/** * sr_classp5_enable() - class enable for a voltage domain * @sr: SR to enable * * When this gets called, we use the h/w loop to setup our voltages * to an calibrated voltage. * * NOTE: Appropriate locks must be held by calling path to ensure mutual * exclusivity */ static int sr_classp5_enable(struct omap_sr *sr) { int res; struct omap_volt_data *volt_data = NULL; struct voltagedomain *voltdm = NULL; struct sr_classp5_calib_data *work_data = NULL; if (IS_ERR_OR_NULL(sr) || IS_ERR_OR_NULL(sr->voltdm)) { pr_err("%s: bad parameters!\n", __func__); return -EINVAL; } work_data = (struct sr_classp5_calib_data *)sr->voltdm_cdata; if (IS_ERR_OR_NULL(work_data)) { pr_err("%s: bad work data %s\n", __func__, sr->name); return -EINVAL; } if (is_idle_task(current)) return 0; voltdm = sr->voltdm; volt_data = omap_voltage_get_curr_vdata(voltdm); if (IS_ERR_OR_NULL(volt_data)) { pr_err("%s: Voltage data is NULL. Cannot enable %s\n", __func__, sr->name); return -ENODATA; } /* * We are resuming from OFF - enable clocks manually to allow OFF-mode. * Clocks will be disabled at "complete" stage by PM Core */ if (sr->suspended) { if (!volt_data->volt_calibrated || work_data->work_active) /* !!! Should never ever be here !!!*/ WARN(true, "Trying to resume with invalid AVS state\n"); else sr->ops->get(sr); } /* We donot expect work item to be active here */ WARN_ON(work_data->work_active); /* If already calibrated, nothing to do here.. */ if (volt_data->volt_calibrated) return 0; res = sr_classp5_start_hw_loop(sr); /* * Calibrate Voltage on first switch to OPP, don't calibrate if called * from system - wide suspend/resume path - it is not allowed to deal * with pm_qos and work scheduling in this case */ if (!res && !volt_data->volt_calibrated && !sr->suspended) sr_classp5_calibration_schedule(sr); return res; }
/** * sr_class1p5_voltdm_recal() - Helper routine to reset calibration. * @voltdm: Voltage domain to reset calibration for * @user: unused * * NOTE: Appropriate locks must be held by calling path to ensure mutual * exclusivity */ static int sr_class1p5_voltdm_recal(struct voltagedomain *voltdm, void *user) { struct omap_volt_data *vdata; /* * we need to go no further if sr is not enabled for this domain or * voltage processor is not present for this voltage domain * (example vdd_wakeup). Class 1.5 requires Voltage processor * to function. */ if (!voltdm->vp || !is_sr_enabled(voltdm)) return 0; vdata = omap_voltage_get_curr_vdata(voltdm); if (!vdata) { pr_err("%s: unable to find current voltage for vdd_%s\n", __func__, voltdm->name); return -ENXIO; } omap_sr_disable(voltdm); omap_voltage_calib_reset(voltdm); voltdm_reset(voltdm); omap_sr_enable(voltdm, vdata); pr_info("%s: %s: calibration reset\n", __func__, voltdm->name); return 0; }
/** * sr_classp5_calibration_schedule() - schedule calibration for sr * @sr: pointer for SR * * configure the AVS for offline calibration */ static int sr_classp5_calibration_schedule(struct omap_sr *sr) { struct sr_classp5_calib_data *work_data; struct omap_volt_data *volt_data; work_data = (struct sr_classp5_calib_data *)sr->voltdm_cdata; if (IS_ERR_OR_NULL(work_data)) { pr_err("%s: bad work data %s\n", __func__, sr->name); return -EINVAL; } volt_data = omap_voltage_get_curr_vdata(sr->voltdm); if (IS_ERR_OR_NULL(volt_data)) { pr_err("%s: Voltage data is NULL. Cannot start work %s\n", __func__, sr->name); return -ENODATA; } work_data->vdata = volt_data; work_data->work_active = true; work_data->num_calib_triggers = 0; /* Dont interrupt me until calibration is complete */ pm_qos_update_request(&work_data->qos, 0); /* program the workqueue and leave it to calibrate offline.. */ schedule_delayed_work(&work_data->work, msecs_to_jiffies(SRP5_SAMPLING_DELAY_MS * SRP5_STABLE_SAMPLES)); return 0; }
/** * sr_classp5_resume_noirq() - class resume_noirq handler * @sr: SmartReflex module which is moving to resume * * The main purpose of resume handler is to * reschedule calibration work if the resume has been started with * un-calibrated voltage. */ static int sr_classp5_resume_noirq(struct omap_sr *sr) { struct omap_volt_data *volt_data; /* * If we resume from suspend and voltage is not calibrated, it means * that calibration work was not completed after re-calibration work * (not scheduled at all or canceled due to suspend or un-calibrated * OPP was selected while suspending/resuming), so reschedule * it here. * * At this stage the code isn't needed to be protected by * omap_dvfs_lock, but - Let's be paranoid (may have smth on other CPUx) * * More over, we shouldn't be here in case if voltage is un-calibrated * voltage, so produce warning. */ mutex_lock(&omap_dvfs_lock); volt_data = omap_voltage_get_curr_vdata(sr->voltdm); if (!volt_data->volt_calibrated) { sr_classp5_calibration_schedule(sr); WARN(true, "sr_classp5: Resume with un-calibrated voltage\n"); } mutex_unlock(&omap_dvfs_lock); return 0; }
/** * omap_vp_enable() - API to enable a particular VP * @voltdm: pointer to the VDD whose VP is to be enabled. * * This API enables a particular voltage processor. Needed by the smartreflex * class drivers. */ void omap_vp_enable(struct voltagedomain *voltdm) { struct omap_vp_instance *vp; u32 vpconfig; struct omap_volt_data *volt; if (IS_ERR_OR_NULL(voltdm)) { pr_err("%s: VDD specified does not exist!\n", __func__); return; } vp = voltdm->vp; if (IS_ERR_OR_NULL(vp)) { pr_err("%s: No VP info for vdd_%s\n", __func__, voltdm->name); return; } if (!voltdm->read || !voltdm->write) { pr_err("%s: No read/write API for accessing vdd_%s regs\n", __func__, voltdm->name); return; } /* If VP is already enabled, do nothing. Return */ if (vp->enabled) return; volt = omap_voltage_get_curr_vdata(voltdm); if (!volt) { pr_warning("%s: unable to find current voltage for %s\n", __func__, voltdm->name); return; } vpconfig = _vp_set_init_voltage(voltdm, omap_get_operation_voltage(volt)); /* Enable VP */ vpconfig |= vp->common->vpconfig_vpenable; voltdm->write(vpconfig, vp->vpconfig); vp->enabled = true; }
static void vp_latch_vsel(struct voltagedomain *voltdm) { struct omap_vp_instance *vp = voltdm->vp; struct omap_volt_data *v = omap_voltage_get_curr_vdata(voltdm); u32 vpconfig; unsigned long uvdc; char vsel; if (IS_ERR_OR_NULL(v)) { pr_warning("%s: unable to get voltage for vdd_%s\n", __func__, voltdm->name); return; } uvdc = omap_get_operation_voltage(v); if (!uvdc) { pr_warning("%s: unable to find current voltage for vdd_%s\n", __func__, voltdm->name); return; } if (!voltdm->pmic || !voltdm->pmic->uv_to_vsel) { pr_warning("%s: PMIC function to convert voltage in uV to" " vsel not registered\n", __func__); return; } vsel = voltdm->pmic->uv_to_vsel(uvdc); vpconfig = voltdm->read(vp->vpconfig); vpconfig &= ~(vp->common->vpconfig_initvoltage_mask | vp->common->vpconfig_initvdd); vpconfig |= vsel << __ffs(vp->common->vpconfig_initvoltage_mask); voltdm->write(vpconfig, vp->vpconfig); /* Trigger initVDD value copy to voltage processor */ voltdm->write((vpconfig | vp->common->vpconfig_initvdd), vp->vpconfig); /* Clear initVDD copy trigger bit */ voltdm->write(vpconfig, vp->vpconfig); }
/** * omap_abb_post_scale() - ABB transition post-voltage scale, if needed * @voltdm: voltage domain that just finished scaling * @target_volt_data: 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, struct omap_volt_data *target_volt_data) { struct omap_abb_instance *abb; struct omap_volt_data *cur_volt_data; /* sanity */ if (!voltdm || !target_volt_data) return -EINVAL; abb = voltdm->abb; if (!abb) return 0; cur_volt_data = omap_voltage_get_curr_vdata(voltdm); if (IS_ERR_OR_NULL(cur_volt_data)) return -EINVAL; /* bail if the sequence is wrong */ if (target_volt_data->volt_nominal < cur_volt_data->volt_nominal) return 0; return omap_abb_set_opp(voltdm, target_volt_data->opp_sel); }
/** * sr_classp5_suspend_noirq() - class suspend_noirq handler * @sr: SmartReflex module which is moving to suspend * * The purpose of suspend_noirq handler is to make sure that Calibration * works are canceled before moving to OFF mode. * Otherwise these works may be executed at any moment, trigger * SmartReflex and race with CPU Idle notifiers. As result - system * will crash */ static int sr_classp5_suspend_noirq(struct omap_sr *sr) { struct sr_classp5_calib_data *work_data; struct omap_volt_data *volt_data; struct voltagedomain *voltdm; int ret = 0; if (IS_ERR_OR_NULL(sr)) { pr_err("%s: bad parameters!\n", __func__); return -EINVAL; } work_data = (struct sr_classp5_calib_data *)sr->voltdm_cdata; if (IS_ERR_OR_NULL(work_data)) { pr_err("%s: bad work data %s\n", __func__, sr->name); return -EINVAL; } /* * At suspend_noirq the code isn't needed to be protected by * omap_dvfs_lock, but - Let's be paranoid (may have smth on other CPUx) */ mutex_lock(&omap_dvfs_lock); voltdm = sr->voltdm; volt_data = omap_voltage_get_curr_vdata(voltdm); if (IS_ERR_OR_NULL(volt_data)) { pr_warning("%s: Voltage data is NULL. Cannot disable %s\n", __func__, sr->name); ret = -ENODATA; goto finish_suspend; } /* * Check if calibration is active at this moment if yes - * abort suspend. */ if (work_data->work_active) { pr_warn("%s: %s Calibration is active, abort suspend (Vnom=%u)\n", __func__, sr->name, volt_data->volt_nominal); ret = -EBUSY; goto finish_suspend; } /* * Check if current voltage is calibrated if no - * abort suspend. */ if (!volt_data->volt_calibrated) { pr_warn("%s: %s Calibration hasn't been done, abort suspend (Vnom=%u)\n", __func__, sr->name, volt_data->volt_nominal); ret = -EBUSY; goto finish_suspend; } /* Let's be paranoid - cancel Calibration work manually */ cancel_delayed_work_sync(&work_data->work); work_data->work_active = false; finish_suspend: mutex_unlock(&omap_dvfs_lock); return ret; }
/** * sr_classp5_disable() - disable for a voltage domain * @sr: SmartReflex module, which need to be disabled * @is_volt_reset: reset the voltage? * * This function has the necessity to either disable SR alone OR disable SR * and reset voltage to appropriate level depending on is_volt_reset parameter. * * NOTE: Appropriate locks must be held by calling path to ensure mutual * exclusivity */ static int sr_classp5_disable(struct omap_sr *sr, int is_volt_reset) { struct voltagedomain *voltdm = NULL; struct omap_volt_data *volt_data = NULL; struct sr_classp5_calib_data *work_data = NULL; if (IS_ERR_OR_NULL(sr) || IS_ERR_OR_NULL(sr->voltdm)) { pr_err("%s: bad parameters!\n", __func__); return -EINVAL; } work_data = (struct sr_classp5_calib_data *)sr->voltdm_cdata; if (IS_ERR_OR_NULL(work_data)) { pr_err("%s: bad work data %s\n", __func__, sr->name); return -EINVAL; } if (is_idle_task(current)) { /* * we should not have seen this path if calibration !complete * pm_qos constraint is already released after voltage * calibration work is finished */ WARN_ON(work_data->work_active); return 0; } /* Rest is regular DVFS path */ voltdm = sr->voltdm; volt_data = omap_voltage_get_curr_vdata(voltdm); if (IS_ERR_OR_NULL(volt_data)) { pr_warning("%s: Voltage data is NULL. Cannot disable %s\n", __func__, sr->name); return -ENODATA; } /* need to do rest of code ONLY if required */ if (volt_data->volt_calibrated && !work_data->work_active) { /* * We are going OFF - disable clocks manually to allow OFF-mode. */ if (sr->suspended) sr->ops->put(sr); return 0; } if (work_data->work_active) { /* flag work is dead and remove the old work */ work_data->work_active = false; cancel_delayed_work_sync(&work_data->work); sr_notifier_control(sr, false); } sr_classp5_stop_hw_loop(sr); if (is_volt_reset) voltdm_reset(sr->voltdm); /* Canceled SR, so no more need to keep request */ pm_qos_update_request(&work_data->qos, PM_QOS_DEFAULT_VALUE); /* * We are going OFF - disable clocks manually to allow OFF-mode. */ if (sr->suspended) { /* !!! Should never ever be here - no guarantee to recover !!!*/ WARN(true, "Trying to go OFF with invalid AVS state\n"); sr->ops->put(sr); } return 0; }