/* * Smart reflex Class 1.5 specific implementations * * Copyright (C) 2010-2011 Texas Instruments, Inc. * Nishanth Menon * * Smart reflex class 1.5 is also called periodic SW Calibration * Some of the highlights are as follows: * – Host CPU triggers OPP calibration when transitioning to non calibrated * OPP * – SR-AVS + VP modules are used to perform calibration * – Once completed, the SmartReflex-AVS module can be disabled * – Enables savings based on process, supply DC accuracy and aging * * 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 #include #include #include #include #include #include #include "smartreflex.h" #include "voltage.h" #include "dvfs.h" #define MAX_VDDS 3 #define SR1P5_SAMPLING_DELAY_MS 1 #define SR1P5_STABLE_SAMPLES 10 #define SR1P5_MAX_TRIGGERS 5 /* * We expect events in 10uS, if we don't receive it in twice as long, * we stop waiting for the event and use the current value */ #define MAX_CHECK_VPTRANS_US 20 /** * struct sr_class1p5_work_data - data meant to be used by calibration work * @work: calibration work * @voltdm: voltage domain for which we are triggering * @vdata: voltage data we are calibrating * @num_calib_triggers: number of triggers from calibration loop * @num_osc_samples: number of samples collected by isr * @u_volt_samples: private data for collecting voltage samples in * case oscillations. filled by the notifier and * consumed by the work item. * @work_active: have we scheduled a work item? * @qos: pm qos handle */ struct sr_class1p5_work_data { struct delayed_work work; struct voltagedomain *voltdm; struct omap_volt_data *vdata; u8 num_calib_triggers; u8 num_osc_samples; unsigned long u_volt_samples[SR1P5_STABLE_SAMPLES]; bool work_active; struct pm_qos_request_list qos; }; #if CONFIG_OMAP_SR_CLASS1P5_RECALIBRATION_DELAY /* recal_work: recalibration calibration work */ static struct delayed_work recal_work; #endif /** * sr_class1p5_notify() - isr notifier for status events * @voltdm: voltage domain for which we were triggered * @voltdm_cdata: voltage domain specific private class data * @status: notifier event to use * * This basically collects data for the work to use. */ static int sr_class1p5_notify(struct voltagedomain *voltdm, void *voltdm_cdata, u32 status) { struct sr_class1p5_work_data *work_data; int idx = 0; if (IS_ERR_OR_NULL(voltdm)) { pr_err("%s: bad parameters!\n", __func__); return -EINVAL; } work_data = (struct sr_class1p5_work_data *)voltdm_cdata; if (IS_ERR_OR_NULL(work_data)) { pr_err("%s:%s no work data!!\n", __func__, voltdm->name); return -EINVAL; } /* Wait for transdone so that we know the voltage to read */ do { if (omap_vp_is_transdone(voltdm)) break; idx++; /* get some constant delay */ udelay(1); } while (idx < MAX_CHECK_VPTRANS_US); /* * NOTE: * If we timeout, we still read the data, * if we are oscillating+irq latencies are too high, we could * have scenarios where we miss transdone event. since * we waited long enough, it is still safe to read the voltage * as we would have waited long enough - Dont warn for this. */ idx = (work_data->num_osc_samples) % SR1P5_STABLE_SAMPLES; work_data->u_volt_samples[idx] = omap_vp_get_curr_volt(voltdm); work_data->num_osc_samples++; omap_vp_clear_transdone(voltdm); return 0; } /** * sr_class1p5_calib_work() - work which actually does the calibration * @work: pointer to the work * * calibration routine uses the following logic: * on the first trigger, we start the isr to collect sr voltages * wait for stabilization delay (reschdule self instead of sleeping) * after the delay, see if we collected any isr events * if none, we have calibrated voltage. * if there are any, we retry untill we giveup. * on retry timeout, select a voltage to use as safe voltage. */ static void sr_class1p5_calib_work(struct work_struct *work) { struct sr_class1p5_work_data *work_data = container_of(work, struct sr_class1p5_work_data, work.work); unsigned long u_volt_safe = 0, u_volt_current = 0, u_volt_margin = 0; struct omap_volt_data *volt_data; struct voltagedomain *voltdm; int idx = 0; if (!work) { pr_err("%s: ooops.. null work_data?\n", __func__); return; } /* * Handle the case where we might have just been scheduled AND * 1.5 disable was called. */ if (!mutex_trylock(&omap_dvfs_lock)) { schedule_delayed_work(&work_data->work, msecs_to_jiffies(SR1P5_SAMPLING_DELAY_MS * SR1P5_STABLE_SAMPLES)); return; } voltdm = work_data->voltdm; /* * In the unlikely case that we did get through when unplanned, * flag and return. */ if (unlikely(!work_data->work_active)) { pr_err("%s:%s unplanned work invocation!\n", __func__, voltdm->name); /* No expectation of calibration, remove qos req */ pm_qos_update_request(&work_data->qos, PM_QOS_DEFAULT_VALUE); mutex_unlock(&omap_dvfs_lock); return; } volt_data = work_data->vdata; work_data->num_calib_triggers++; /* if we are triggered first time, we need to start isr to sample */ if (work_data->num_calib_triggers == 1) { /* We could be interrupted many times, so, only for debug */ pr_debug("%s: %s: Calibration start: Voltage Nominal=%d\n", __func__, voltdm->name, volt_data->volt_nominal); goto start_sampling; } /* Stop isr from interrupting our measurements :) */ sr_notifier_control(voltdm, false); /* * Quit sampling * a) if we have oscillations * b) if we have nominal voltage as the voltage */ if (work_data->num_calib_triggers == SR1P5_MAX_TRIGGERS) goto stop_sampling; /* if there are no samples captured.. SR is silent, aka stability! */ if (!work_data->num_osc_samples) { /* Did we interrupt too early? */ u_volt_current = omap_vp_get_curr_volt(voltdm); if (u_volt_current >= volt_data->volt_nominal) goto start_sampling; u_volt_safe = u_volt_current; goto done_calib; } /* we have potential oscillations/first sample */ start_sampling: work_data->num_osc_samples = 0; /* Clear transdone events so that we can go on. */ do { if (!omap_vp_is_transdone(voltdm)) break; idx++; /* get some constant delay */ udelay(1); omap_vp_clear_transdone(voltdm); } while (idx < MAX_CHECK_VPTRANS_US); if (idx >= MAX_CHECK_VPTRANS_US) pr_warning("%s: timed out waiting for transdone clear!!\n", __func__); /* Clear pending events */ sr_notifier_control(voltdm, false); /* trigger sampling */ sr_notifier_control(voltdm, true); schedule_delayed_work(&work_data->work, msecs_to_jiffies(SR1P5_SAMPLING_DELAY_MS * SR1P5_STABLE_SAMPLES)); mutex_unlock(&omap_dvfs_lock); return; stop_sampling: /* * We are here for Oscillations due to two scenarios: * a) SR is attempting to adjust voltage lower than VLIMITO * which VP will ignore, but SR will re-attempt * b) actual oscillations * NOTE: For debugging, enable debug to see the samples. */ pr_warning("%s: %s Stop sampling: Voltage Nominal=%d samples=%d\n", __func__, work_data->voltdm->name, volt_data->volt_nominal, work_data->num_osc_samples); /* pick up current voltage */ u_volt_current = omap_vp_get_curr_volt(voltdm); /* Just in case we got more interrupts than our tiny buffer */ if (work_data->num_osc_samples > SR1P5_STABLE_SAMPLES) idx = SR1P5_STABLE_SAMPLES; else idx = work_data->num_osc_samples; /* Index at 0 */ idx -= 1; u_volt_safe = u_volt_current; /* Grab the max of the samples as the stable voltage */ for (; idx >= 0; idx--) { pr_debug("%s: osc_v[%d]=%ld, safe_v=%ld\n", __func__, idx, work_data->u_volt_samples[idx], u_volt_safe); if (work_data->u_volt_samples[idx] > u_volt_safe) u_volt_safe = work_data->u_volt_samples[idx]; } /* Fall through to close up common stuff */ done_calib: sr_disable_errgen(voltdm); omap_vp_disable(voltdm); sr_disable(voltdm); /* Add margin if needed */ if (volt_data->volt_margin) { struct omap_voltdm_pmic *pmic = voltdm->pmic; /* Convert to rounded to PMIC step level if available */ if (pmic && pmic->vsel_to_uv && pmic->uv_to_vsel) { /* * To ensure conversion works: * use a proper base voltage - we use the current volt * then convert it with pmic routine to vsel and back * to voltage, and finally remove the base voltage */ u_volt_margin = u_volt_current + volt_data->volt_margin; u_volt_margin = pmic->uv_to_vsel(u_volt_margin); u_volt_margin = pmic->vsel_to_uv(u_volt_margin); u_volt_margin -= u_volt_current; } else { u_volt_margin = volt_data->volt_margin; } u_volt_safe += u_volt_margin; } /* just warn, dont clamp down on voltage */ if (u_volt_safe > volt_data->volt_nominal) { pr_warning("%s: %s Vsafe %ld > Vnom %d. %ld[%d] margin on" "vnom %d curr_v=%ld\n", __func__, voltdm->name, u_volt_safe, volt_data->volt_nominal, u_volt_margin, volt_data->volt_margin, volt_data->volt_nominal, u_volt_current); } volt_data->volt_calibrated = u_volt_safe; /* Setup my dynamic voltage for the next calibration for this opp */ volt_data->volt_dynamic_nominal = omap_get_dyn_nominal(volt_data); /* * if the voltage we decided as safe is not the current voltage, * switch */ if (volt_data->volt_calibrated != u_volt_current) { pr_debug("%s: %s reconfiguring to voltage %d\n", __func__, voltdm->name, volt_data->volt_calibrated); voltdm_scale(voltdm, volt_data); } pr_info("%s: %s: Calibration complete: Voltage:Nominal=%d " "Calib=%d margin=%d\n", __func__, voltdm->name, volt_data->volt_nominal, volt_data->volt_calibrated, volt_data->volt_margin); /* * TODO: Setup my wakeup voltage to allow immediate going to OFF and * on - Pending twl and voltage layer cleanups. * This is necessary, as this is not done as part of regular * Dvfs flow. * vc_setup_on_voltage(voltdm, volt_data->volt_calibrated); */ work_data->work_active = false; /* Calibration done, Remove qos req */ pm_qos_update_request(&work_data->qos, PM_QOS_DEFAULT_VALUE); mutex_unlock(&omap_dvfs_lock); } #if CONFIG_OMAP_SR_CLASS1P5_RECALIBRATION_DELAY /** * 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_class1p5_recal_work() - work which actually does the calibration * @work: pointer to the work * * on a periodic basis, we come and reset our calibration setup * so that a recalibration of the OPPs take place. This takes * care of aging factor in the system. */ static void sr_class1p5_recal_work(struct work_struct *work) { mutex_lock(&omap_dvfs_lock); if (voltdm_for_each(sr_class1p5_voltdm_recal, NULL)) pr_err("%s: Recalibration failed\n", __func__); mutex_unlock(&omap_dvfs_lock); /* We come back again after time the usual delay */ schedule_delayed_work(&recal_work, msecs_to_jiffies (CONFIG_OMAP_SR_CLASS1P5_RECALIBRATION_DELAY)); } #endif /* CONFIG_OMAP_SR_CLASS1P5_RECALIBRATION_DELAY */ /** * sr_class1p5_enable() - class 1.5 mode of enable for a voltage domain * @voltdm: voltage domain to enable SR for * @voltdm_cdata: voltage domain specific private class data * @volt_data: voltdata for the current OPP being transitioned to * * when this gets called, we use the h/w loop to setup our voltages * to an calibrated voltage, detect any oscillations, recover from the same * and finally store the optimized voltage as the calibrated voltage in the * system. * * NOTE: Appropriate locks must be held by calling path to ensure mutual * exclusivity */ static int sr_class1p5_enable(struct voltagedomain *voltdm, void *voltdm_cdata, struct omap_volt_data *volt_data) { int r; struct sr_class1p5_work_data *work_data; if (IS_ERR_OR_NULL(voltdm) || IS_ERR_OR_NULL(volt_data)) { pr_err("%s: bad parameters!\n", __func__); return -EINVAL; } /* If already calibrated, nothing to do here.. */ if (volt_data->volt_calibrated) return 0; work_data = (struct sr_class1p5_work_data *)voltdm_cdata; if (IS_ERR_OR_NULL(work_data)) { pr_err("%s: bad work data??\n", __func__); return -EINVAL; } if (work_data->work_active) return 0; omap_vp_enable(voltdm); r = sr_enable(voltdm, volt_data); if (r) { pr_err("%s: sr[%s] failed\n", __func__, voltdm->name); sr_disable_errgen(voltdm); omap_vp_disable(voltdm); return r; } work_data->vdata = volt_data; work_data->work_active = true; work_data->num_calib_triggers = 0; /* Dont interrupt me untill 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(SR1P5_SAMPLING_DELAY_MS * SR1P5_STABLE_SAMPLES)); return 0; } /** * sr_class1p5_disable() - disable 1.5 mode for a voltage domain * @voltdm: voltage domain for the sr which needs disabling * @volt_data: voltage data for current OPP to disable * @voltdm_cdata: voltage domain specific private class data * @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. * * Disabling SR H/w loop: * If calibration is complete or not yet triggered, we have no need to disable * SR h/w loop. * If calibration is complete, we would have already disabled SR AVS at the end * of calibration and h/w loop is inactive when this is called. * If it was never calibrated before, H/w loop was never enabled in the first * place to disable. * If calibration is underway, we cancel the work queue and disable SR. This is * to provide priority to DVFS transition as such transitions cannot wait * without impacting user experience. * * Resetting voltage: * If we have already completed calibration, then resetting to nominal voltage * is not required as we are functioning at safe voltage levels. * If we have not started calibration, we would like to reset to nominal voltage * If calibration is underway and we are attempting to reset voltage as * well, it implies we are in idle/suspend paths where we give priority * to calibration activity and a retry will be attempted. * * NOTE: Appropriate locks must be held by calling path to ensure mutual * exclusivity */ static int sr_class1p5_disable(struct voltagedomain *voltdm, void *voltdm_cdata, struct omap_volt_data *volt_data, int is_volt_reset) { struct sr_class1p5_work_data *work_data; if (IS_ERR_OR_NULL(voltdm) || IS_ERR_OR_NULL(volt_data)) { pr_err("%s: bad parameters!\n", __func__); return -EINVAL; } work_data = (struct sr_class1p5_work_data *)voltdm_cdata; if (IS_ERR_OR_NULL(work_data)) { pr_err("%s: bad work data??\n", __func__); return -EINVAL; } if (work_data->work_active) { /* if volt reset and work is active, we dont allow this */ if (is_volt_reset) return -EBUSY; /* flag work is dead and remove the old work */ work_data->work_active = false; cancel_delayed_work_sync(&work_data->work); sr_notifier_control(voltdm, false); sr_disable_errgen(voltdm); omap_vp_disable(voltdm); sr_disable(voltdm); /* Cancelled SR, so no more need to keep request */ pm_qos_update_request(&work_data->qos, PM_QOS_DEFAULT_VALUE); } /* If already calibrated, don't need to reset voltage */ if (volt_data->volt_calibrated) return 0; if (is_volt_reset) voltdm_reset(voltdm); return 0; } /** * sr_class1p5_configure() - configuration function * @voltdm: configure for which voltage domain * @voltdm_cdata: voltage domain specific private class data * * we dont do much here other than setup some registers for * the sr module involved. */ static int sr_class1p5_configure(struct voltagedomain *voltdm, void *voltdm_cdata) { if (IS_ERR_OR_NULL(voltdm)) { pr_err("%s: bad parameters!\n", __func__); return -EINVAL; } return sr_configure_errgen(voltdm); } /** * sr_class1p5_init() - class 1p5 init * @voltdm: sr voltage domain * @voltdm_cdata: voltage domain specific private class data * allocated by class init with work item data * freed by deinit. * @class_priv_data: private data for the class (unused) * * we do class specific initialization like creating sysfs/debugfs entries * needed, spawning of a kthread if needed etc. */ static int sr_class1p5_init(struct voltagedomain *voltdm, void **voltdm_cdata, void *class_priv_data) { struct sr_class1p5_work_data *work_data; if (IS_ERR_OR_NULL(voltdm) || IS_ERR_OR_NULL(voltdm_cdata)) { pr_err("%s: bad parameters!\n", __func__); return -EINVAL; } if (!IS_ERR_OR_NULL(*voltdm_cdata)) { pr_err("%s: ooopps.. class already initialized for %s! bug??\n", __func__, voltdm->name); return -EINVAL; } /* setup our work params */ work_data = kzalloc(sizeof(struct sr_class1p5_work_data), GFP_KERNEL); if (!work_data) { pr_err("%s: no memory to allocate work data on domain %s\n", __func__, voltdm->name); return -ENOMEM; } work_data->voltdm = voltdm; INIT_DELAYED_WORK_DEFERRABLE(&work_data->work, sr_class1p5_calib_work); *voltdm_cdata = (void *)work_data; pm_qos_add_request(&work_data->qos, PM_QOS_CPU_DMA_LATENCY, PM_QOS_DEFAULT_VALUE); return 0; } /** * sr_class1p5_deinit() - class 1p5 deinitialization * @voltdm: voltage domain for which to do this. * @voltdm_cdata: voltage domain specific private class data * allocated by class init with work item data * freed by deinit. * @class_priv_data: class private data for deinitialiation (unused) * * currently only resets the calibrated voltage forcing DVFS voltages * to be used in the system * * NOTE: Appropriate locks must be held by calling path to ensure mutual * exclusivity */ static int sr_class1p5_deinit(struct voltagedomain *voltdm, void **voltdm_cdata, void *class_priv_data) { struct sr_class1p5_work_data *work_data; if (IS_ERR_OR_NULL(voltdm) || IS_ERR_OR_NULL(voltdm_cdata)) { pr_err("%s: bad parameters!\n", __func__); return -EINVAL; } if (IS_ERR_OR_NULL(*voltdm_cdata)) { pr_err("%s: ooopps.. class not initialized for %s! bug??\n", __func__, voltdm->name); return -EINVAL; } work_data = (struct sr_class1p5_work_data *) *voltdm_cdata; /* * we dont have SR periodic calib anymore.. so reset calibs * we are already protected by appropriate locks, so no lock needed * here. */ if (work_data->work_active) sr_class1p5_disable(voltdm, work_data, work_data->vdata, 0); /* Ensure worker canceled. */ cancel_delayed_work_sync(&work_data->work); omap_voltage_calib_reset(voltdm); voltdm_reset(voltdm); pm_qos_remove_request(&work_data->qos); *voltdm_cdata = NULL; kfree(work_data); return 0; } /* SR class1p5 structure */ static struct omap_sr_class_data class1p5_data = { .enable = sr_class1p5_enable, .disable = sr_class1p5_disable, .configure = sr_class1p5_configure, .class_type = SR_CLASS1P5, .init = sr_class1p5_init, .deinit = sr_class1p5_deinit, .notify = sr_class1p5_notify, /* * trigger for bound - this tells VP that SR has a voltage * change. we should try and ensure transdone is set before reading * vp voltage. */ .notify_flags = SR_NOTIFY_MCUBOUND, }; /** * sr_class1p5_driver_init() - register class 1p5 as default * * board files call this function to use class 1p5, we register with the * smartreflex subsystem */ static int __init sr_class1p5_driver_init(void) { int r; /* Enable this class only for OMAP3630 and OMAP4 */ if (!(cpu_is_omap3630() || cpu_is_omap44xx())) return -EINVAL; r = sr_register_class(&class1p5_data); if (r) { pr_err("SmartReflex class 1.5 driver: " "failed to register with %d\n", r); } else { #if CONFIG_OMAP_SR_CLASS1P5_RECALIBRATION_DELAY INIT_DELAYED_WORK_DEFERRABLE(&recal_work, sr_class1p5_recal_work); schedule_delayed_work(&recal_work, msecs_to_jiffies (CONFIG_OMAP_SR_CLASS1P5_RECALIBRATION_DELAY)); #endif pr_info("SmartReflex class 1.5 driver: initialized (%dms)\n", CONFIG_OMAP_SR_CLASS1P5_RECALIBRATION_DELAY); } return r; } late_initcall(sr_class1p5_driver_init);