Exemplo n.º 1
0
/**
 * 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;
}
Exemplo n.º 2
0
/**
 * 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;
}
Exemplo n.º 3
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;
}
Exemplo n.º 4
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;
}
Exemplo n.º 5
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;
}
Exemplo n.º 6
0
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);
}
Exemplo n.º 7
0
/**
 * 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);
}
Exemplo n.º 8
0
/**
 * 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;
}
Exemplo n.º 9
0
/**
 * 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;
}