From patchwork Wed Mar 2 10:55:30 2011 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Nishanth Menon X-Patchwork-Id: 602811 X-Patchwork-Delegate: khilman@deeprootsystems.com Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by demeter1.kernel.org (8.14.4/8.14.3) with ESMTP id p22AwB8D009481 for ; Wed, 2 Mar 2011 10:58:16 GMT Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1757198Ab1CBK6P (ORCPT ); Wed, 2 Mar 2011 05:58:15 -0500 Received: from na3sys009aog113.obsmtp.com ([74.125.149.209]:60393 "EHLO na3sys009aog113.obsmtp.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1757116Ab1CBK6O (ORCPT ); Wed, 2 Mar 2011 05:58:14 -0500 Received: from source ([209.85.218.49]) (using TLSv1) by na3sys009aob113.postini.com ([74.125.148.12]) with SMTP ID DSNKTW4ixMbsZAdf2MTv+1A6iQJTWbgpdLYq@postini.com; Wed, 02 Mar 2011 02:58:13 PST Received: by yic15 with SMTP id 15so4096388yic.22 for ; Wed, 02 Mar 2011 02:58:12 -0800 (PST) Received: by 10.150.238.10 with SMTP id l10mr10384201ybh.262.1299063492233; Wed, 02 Mar 2011 02:58:12 -0800 (PST) Received: from localhost (dragon.ti.com [192.94.94.33]) by mx.google.com with ESMTPS id q18sm3148760ybk.23.2011.03.02.02.58.04 (version=TLSv1/SSLv3 cipher=OTHER); Wed, 02 Mar 2011 02:58:10 -0800 (PST) From: Nishanth Menon To: linux-omap Cc: Kevin H , Tony L , linux-arm , Nishanth Menon , Ambresh K , Eduardo Valentin , Igor Dmitriev , Paul , "Peter 'p2' De Schrijver" Subject: [PATCH v2 17/18] omap3630+: sr: add support for class 1.5 Date: Wed, 2 Mar 2011 16:25:30 +0530 Message-Id: <1299063331-27968-18-git-send-email-nm@ti.com> X-Mailer: git-send-email 1.7.1 In-Reply-To: <1299063331-27968-1-git-send-email-nm@ti.com> References: <1299063331-27968-1-git-send-email-nm@ti.com> Sender: linux-omap-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-omap@vger.kernel.org X-Greylist: IP, sender and recipient auto-whitelisted, not delayed by milter-greylist-4.2.6 (demeter1.kernel.org [140.211.167.41]); Wed, 02 Mar 2011 10:58:16 +0000 (UTC) diff --git a/arch/arm/mach-omap2/Makefile b/arch/arm/mach-omap2/Makefile index 1c0c2b0..1a82e6d 100644 --- a/arch/arm/mach-omap2/Makefile +++ b/arch/arm/mach-omap2/Makefile @@ -66,6 +66,7 @@ obj-$(CONFIG_ARCH_OMAP4) += pm44xx.o voltage.o pm_bus.o obj-$(CONFIG_PM_DEBUG) += pm-debug.o obj-$(CONFIG_OMAP_SMARTREFLEX) += sr_device.o smartreflex.o obj-$(CONFIG_OMAP_SMARTREFLEX_CLASS3) += smartreflex-class3.o +obj-$(CONFIG_OMAP_SMARTREFLEX_CLASS1P5) += smartreflex-class1p5.o AFLAGS_sleep24xx.o :=-Wa,-march=armv6 AFLAGS_sleep34xx.o :=-Wa,-march=armv7-a diff --git a/arch/arm/mach-omap2/smartreflex-class1p5.c b/arch/arm/mach-omap2/smartreflex-class1p5.c new file mode 100644 index 0000000..14ddb42 --- /dev/null +++ b/arch/arm/mach-omap2/smartreflex-class1p5.c @@ -0,0 +1,582 @@ +/* + * 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 + +#define MAX_VDDS 3 +#define SR1P5_SAMPLING_DELAY_MS 1 +#define SR1P5_STABLE_SAMPLES 5 +#define SR1P5_MAX_TRIGGERS 5 + +/* + * we expect events in 10uS, if we dont get 2wice times as much, + * we could kind of ignore this as a missed event. + */ +#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 + * @work_active: have we scheduled a work item? + */ +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; + bool work_active; +}; + +#if CONFIG_OMAP_SR_CLASS1P5_RECALIBRATION_DELAY +/* recal_work: recalibration calibration work */ +static struct delayed_work recal_work; +#endif + +/** + * struct sr_class1p5_data - private data for class 1p5 + * @work_data: work item data per voltage domain + */ +struct sr_class1p5_data { + struct sr_class1p5_work_data work_data[MAX_VDDS]; +}; + +static void sr_class1p5_reset_calib(struct voltagedomain *voltdm, bool reset, + bool recal); + +/* our instance of class 1p5 private data */ +static struct sr_class1p5_data class_1p5_data; + +static struct sr_class1p5_work_data *get_sr1p5_work(struct voltagedomain + *voltdm) +{ + int idx; + for (idx = 0; idx < MAX_VDDS; idx++) { + if (class_1p5_data.work_data[idx].voltdm && !strcmp + (class_1p5_data.work_data[idx].voltdm->name, voltdm->name)) + return &class_1p5_data.work_data[idx]; + } + return ERR_PTR(-ENODATA); +} + +/** + * sr_class1p5_notify() - isr notifier for status events + * @voltdm: voltage domain for which we were triggered + * @status: notifier event to use + * + * This basically collects data for the work to use. + */ +static int sr_class1p5_notify(struct voltagedomain *voltdm, 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 = get_sr1p5_work(voltdm); + if (unlikely(!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); + + /* + * 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 - still flag it.. + */ + if (idx >= MAX_CHECK_VPTRANS_US) + pr_warning("%s: timed out waiting for transdone!!\n", __func__); + + omap_vp_clear_transdone(voltdm); + + idx = (work_data->num_osc_samples) % SR1P5_STABLE_SAMPLES; + work_data->num_osc_samples++; + + return 0; +} + +/** + * do_calibrate() - 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 do_calibrate(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; + struct omap_volt_data *volt_data; + struct voltagedomain *voltdm; + + if (unlikely(!work_data)) { + pr_err("%s: ooops.. null work_data?\n", __func__); + return; + } + + /* + * TODO:Handle the case where we might have just been scheduled AND + * 1.5 disable was called. check and HOLD dvfs + */ + + 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); + /* TODO release the dvfs */ + return; + } + + 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) + goto start_sampling; + + /* Stop isr from interrupting our measurements :) */ + sr_notifier_control(voltdm, false); + + volt_data = work_data->vdata; + + /* if there are no samples captured.. SR is silent, aka stability! */ + if (!work_data->num_osc_samples) { + u_volt_safe = omap_vp_get_curr_volt(voltdm); + u_volt_current = u_volt_safe; + goto done_calib; + } + if (work_data->num_calib_triggers == SR1P5_MAX_TRIGGERS) { + pr_warning("%s: %s recalib timeout!\n", __func__, + work_data->voltdm->name); + goto oscillating_calib; + } + + /* we have potential oscillations/first sample */ +start_sampling: + work_data->num_osc_samples = 0; + /* Clear pending events */ + sr_notifier_control(voltdm, false); + /* Clear all transdones */ + while (omap_vp_is_transdone(voltdm)) + omap_vp_clear_transdone(voltdm); + /* trigger sampling */ + sr_notifier_control(voltdm, true); + schedule_delayed_work(&work_data->work, + msecs_to_jiffies(SR1P5_SAMPLING_DELAY_MS * + SR1P5_STABLE_SAMPLES)); + /* TODO: release dvfs */ + return; + +oscillating_calib: + /* Use the nominal voltage as the safe voltage */ + u_volt_safe = volt_data->volt_nominal; + /* pick up current voltage to switch if needed */ + u_volt_current = omap_vp_get_curr_volt(voltdm); + + /* Fall through to close up common stuff */ +done_calib: + omap_vp_disable(voltdm); + sr_disable(voltdm); + + 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); + omap_voltage_scale_vdd(voltdm, volt_data); + } + + /* + * 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; + /* TODO: release dvfs */ +} + +#if CONFIG_OMAP_SR_CLASS1P5_RECALIBRATION_DELAY +/** + * do_recalibrate() - 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 do_recalibrate(struct work_struct *work) +{ + struct voltagedomain *voltdm; + int idx; + static struct sr_class1p5_work_data *work_data; + + for (idx = 0; idx < MAX_VDDS; idx++) { + work_data = &class_1p5_data.work_data[idx]; + voltdm = work_data->voltdm; + if (voltdm) { + /* if sr is not enabled, we check later */ + if (!is_sr_enabled(voltdm)) + continue; + /* TODO: Pause the dvfs transitions */ + /* if sr is not enabled, we check later */ + + /* Reset and force a recalibration for current opp */ + sr_class1p5_reset_calib(voltdm, true, true); + + /* TODO: unpause DVFS transitions */ + } + } + /* 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 + * @voltdm: voltage domain to enable SR for + * @volt_data: voltdata to the voltage transition taking place + * + * 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 + */ +static int sr_class1p5_enable(struct voltagedomain *voltdm, + 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 = get_sr1p5_work(voltdm); + if (unlikely(!work_data)) { + pr_err("%s: aieeee.. 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); + omap_vp_disable(voltdm); + return r; + } + work_data->vdata = volt_data; + work_data->work_active = true; + work_data->num_calib_triggers = 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 for class 1p5 + * @voltdm: voltage domain for the sr which needs disabling + * @volt_data: voltagedata to disable + * @is_volt_reset: reset the voltage? + * + * we dont do anything if the class 1p5 is being used. this is because we + * already disable sr at the end of calibration and no h/w loop is actually + * active when this is called. + */ +static int sr_class1p5_disable(struct voltagedomain *voltdm, + 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 = get_sr1p5_work(voltdm); + 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); + omap_vp_disable(voltdm); + sr_disable(voltdm); + } + + /* if already calibrated, nothin special to do here.. */ + if (volt_data->volt_calibrated) + return 0; + + if (is_volt_reset) + omap_voltage_reset(voltdm); + return 0; +} + +/** + * sr_class1p5_configure() - configuration function + * @voldm: configure for which voltage domain + * + * we dont do much here other than setup some registers for + * the sr module involved. + */ +static int sr_class1p5_configure(struct voltagedomain *voltdm) +{ + if (IS_ERR_OR_NULL(voltdm)) { + pr_err("%s: bad parameters!\n", __func__); + return -EINVAL; + } + + return sr_configure_errgen(voltdm); +} + +/** + * sr_class1p5_reset_calib() - reset all calibrated voltages + * @srid: srid to reset the calibration for + * @reset: reset voltage before we recal? + * @recal: should I recalibrate my current opp? + * + * if we call this, it means either periodic calibration trigger was + * fired(either from sysfs or other mechanisms) or we have disabled class 1p5, + * meaning we cant trust the calib voltages anymore, it is better to use + * nominal in the system + */ +static void sr_class1p5_reset_calib(struct voltagedomain *voltdm, bool reset, + bool recal) +{ + struct sr_class1p5_work_data *work_data; + + /* I dont need to go further if sr is not present */ + if (!is_sr_enabled(voltdm)) + return; + + work_data = get_sr1p5_work(voltdm); + + if (work_data->work_active) + sr_class1p5_disable(voltdm, work_data->vdata, 0); + + omap_voltage_calib_reset(voltdm); + + /* + * I should now reset the voltages to my nominal to be safe + */ + if (reset) + omap_voltage_reset(voltdm); + + /* + * I should fire a recalibration for current opp if needed + * Note: i have just reset my calibrated voltages, and if + * i call sr_enable equivalent, I will cause a recalibration + * loop, even though the function is called sr_enable.. we + * are in class 1.5 ;) + */ + if (reset && recal) + sr_class1p5_enable(voltdm, work_data->vdata); +} + +/** + * sr_class1p5_cinit() - class 1p5 init + * @voltdm: sr voltage domain + * @class_priv_data: private data for the class + * + * we do class specific initialization like creating sysfs/debugfs entries + * needed, spawning of a kthread if needed etc. + */ +static int sr_class1p5_cinit(struct voltagedomain *voltdm, + void *class_priv_data) +{ + struct sr_class1p5_work_data *work_data; + int idx; + + if (IS_ERR_OR_NULL(voltdm) || IS_ERR_OR_NULL(class_priv_data)) { + pr_err("%s: bad parameters!\n", __func__); + return -EINVAL; + } + + /* setup our work params */ + work_data = get_sr1p5_work(voltdm); + if (!IS_ERR_OR_NULL(work_data)) { + pr_err("%s: ooopps.. class already initialized for %s! bug??\n", + __func__, voltdm->name); + return -EINVAL; + } + work_data = NULL; + /* get the next spare work_data */ + for (idx = 0; idx < MAX_VDDS; idx++) { + if (!class_1p5_data.work_data[idx].voltdm) { + work_data = &class_1p5_data.work_data[idx]; + break; + } + } + if (!work_data) { + pr_err("%s: no more space for work data for domains!\n", + __func__); + return -ENOMEM; + } + work_data->voltdm = voltdm; + INIT_DELAYED_WORK_DEFERRABLE(&work_data->work, do_calibrate); + return 0; +} + +/** + * sr_class1p5_cdeinit() - class 1p5 deinitialization + * @voltdm: voltage domain for which to do this. + * @class_priv_data: class private data for deinitialiation + * + * currently only resets the calibrated voltage forcing dvfs voltages + * to be used in the system + */ +static int sr_class1p5_cdeinit(struct voltagedomain *voltdm, + void *class_priv_data) +{ + struct sr_class1p5_work_data *work_data; + + if (IS_ERR_OR_NULL(voltdm) || IS_ERR_OR_NULL(class_priv_data)) { + pr_err("%s: bad parameters!\n", __func__); + return -EINVAL; + } + + /* setup our work params */ + work_data = get_sr1p5_work(voltdm); + if (IS_ERR_OR_NULL(work_data)) { + pr_err("%s: ooopps.. class not initialized for %s! bug??\n", + __func__, voltdm->name); + return -EINVAL; + } + + /* + * we dont have SR periodic calib anymore.. so reset calibs + * we are already protected by sr debugfs lock, so no lock needed + * here. + */ + sr_class1p5_reset_calib(voltdm, true, false); + + /* reset all data for this work data */ + memset(work_data, 0, sizeof(*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, + .class_init = sr_class1p5_cinit, + .class_deinit = sr_class1p5_cdeinit, + .notify = sr_class1p5_notify, + /* + * trigger for bound - this tells VP that SR has a voltage + * change. we should ensure transdone is set before reading + * vp voltage. + */ + .notify_flags = SR_NOTIFY_MCUBOUND, + .class_priv_data = (void *)&class_1p5_data, +}; + +/** + * sr_class1p5_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_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, do_recalibrate); + 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_init); diff --git a/arch/arm/mach-omap2/smartreflex-class3.c b/arch/arm/mach-omap2/smartreflex-class3.c index 7ac88da..5f7a33e 100644 --- a/arch/arm/mach-omap2/smartreflex-class3.c +++ b/arch/arm/mach-omap2/smartreflex-class3.c @@ -21,7 +21,9 @@ static int sr_class3_enable(struct voltagedomain *voltdm, return sr_enable(voltdm, volt_data); } -static int sr_class3_disable(struct voltagedomain *voltdm, int is_volt_reset) +static int sr_class3_disable(struct voltagedomain *voltdm, + struct omap_volt_data *vdata, + int is_volt_reset) { omap_vp_disable(voltdm); sr_disable(voltdm); diff --git a/arch/arm/mach-omap2/smartreflex.c b/arch/arm/mach-omap2/smartreflex.c index 9b9d380..2e74b1e 100644 --- a/arch/arm/mach-omap2/smartreflex.c +++ b/arch/arm/mach-omap2/smartreflex.c @@ -317,7 +317,9 @@ static void sr_stop_vddautocomp(struct omap_sr *sr) } if (sr->autocomp_active) { - sr_class->disable(sr->voltdm, 1); + sr_class->disable(sr->voltdm, + omap_voltage_get_nom_volt(sr->voltdm), + 1); if (sr_class->class_deinit && sr_class->class_deinit(sr->voltdm, sr_class->class_priv_data)) { @@ -471,6 +473,28 @@ static u32 sr_retrieve_nvalue(struct omap_sr *sr, u32 efuse_offs) /* Public Functions */ /** + * is_sr_enabled() - is Smart reflex enabled for this domain? + * @voltdm: voltage domain to check + * + * Returns 0 if SR is enabled for this domain, else returns err + */ +bool is_sr_enabled(struct voltagedomain *voltdm) +{ + struct omap_sr *sr; + if (IS_ERR_OR_NULL(voltdm)) { + pr_warning("%s: invalid param voltdm\n", __func__); + return false; + } + sr = _sr_lookup(voltdm); + if (IS_ERR(sr)) { + pr_warning("%s: omap_sr struct for sr_%s not found\n", + __func__, voltdm->name); + return false; + } + return sr->autocomp_active; +} + +/** * sr_configure_errgen() - Configures the smrtreflex to perform AVS using the * error generator module. * @voltdm: VDD pointer to which the SR module to be configured belongs to. @@ -839,6 +863,7 @@ void omap_sr_enable(struct voltagedomain *voltdm, * omap_sr_disable() - API to disable SR without resetting the voltage * processor voltage * @voltdm: VDD pointer to which the SR module to be configured belongs to. + * @volt_data: Voltage data to go to * * This API is to be called from the kernel in order to disable * a particular smartreflex module. This API will in turn call @@ -846,7 +871,8 @@ void omap_sr_enable(struct voltagedomain *voltdm, * the smartreflex class disable not to reset the VP voltage after * disabling smartreflex. */ -void omap_sr_disable(struct voltagedomain *voltdm) +void omap_sr_disable(struct voltagedomain *voltdm, + struct omap_volt_data *vdata) { struct omap_sr *sr = _sr_lookup(voltdm); @@ -865,7 +891,7 @@ void omap_sr_disable(struct voltagedomain *voltdm) return; } - sr_class->disable(voltdm, 0); + sr_class->disable(voltdm, vdata, 0); } /** @@ -898,7 +924,7 @@ void omap_sr_disable_reset_volt(struct voltagedomain *voltdm) return; } - sr_class->disable(voltdm, 1); + sr_class->disable(voltdm, omap_voltage_get_nom_volt(voltdm), 1); } /** diff --git a/arch/arm/mach-omap2/voltage.c b/arch/arm/mach-omap2/voltage.c index 3cbe450..da0129b 100644 --- a/arch/arm/mach-omap2/voltage.c +++ b/arch/arm/mach-omap2/voltage.c @@ -379,9 +379,55 @@ static int nom_volt_debug_get(void *data, u64 *val) return 0; } +static int dyn_volt_debug_get(void *data, u64 *val) +{ + struct omap_vdd_info *vdd = (struct omap_vdd_info *) data; + struct omap_volt_data *volt_data; + + if (!vdd) { + pr_warning("Wrong paramater passed\n"); + return -EINVAL; + } + + volt_data = omap_voltage_get_nom_volt(&vdd->voltdm); + if (IS_ERR_OR_NULL(volt_data)) { + pr_warning("%s: No voltage/domain?\n", __func__); + return -ENODEV; + } + + *val = volt_data->volt_dynamic_nominal; + + return 0; +} + +static int calib_volt_debug_get(void *data, u64 *val) +{ + struct omap_vdd_info *vdd = (struct omap_vdd_info *) data; + struct omap_volt_data *volt_data; + + if (!vdd) { + pr_warning("Wrong paramater passed\n"); + return -EINVAL; + } + + volt_data = omap_voltage_get_nom_volt(&vdd->voltdm); + if (IS_ERR_OR_NULL(volt_data)) { + pr_warning("%s: No voltage/domain?\n", __func__); + return -ENODEV; + } + + *val = volt_data->volt_calibrated; + + return 0; +} + DEFINE_SIMPLE_ATTRIBUTE(vp_volt_debug_fops, vp_volt_debug_get, NULL, "%llu\n"); DEFINE_SIMPLE_ATTRIBUTE(nom_volt_debug_fops, nom_volt_debug_get, NULL, "%llu\n"); +DEFINE_SIMPLE_ATTRIBUTE(dyn_volt_debug_fops, dyn_volt_debug_get, NULL, + "%llu\n"); +DEFINE_SIMPLE_ATTRIBUTE(calib_volt_debug_fops, calib_volt_debug_get, NULL, + "%llu\n"); static void vp_latch_vsel(struct omap_vdd_info *vdd) { u32 vpconfig; @@ -509,6 +555,12 @@ static void __init vdd_debugfs_init(struct omap_vdd_info *vdd) (void) debugfs_create_file("curr_nominal_volt", S_IRUGO, vdd->debug_dir, (void *) vdd, &nom_volt_debug_fops); + (void) debugfs_create_file("curr_dyn_nominal_volt", S_IRUGO, + vdd->debug_dir, (void *) vdd, + &dyn_volt_debug_fops); + (void) debugfs_create_file("curr_calibrated_volt", S_IRUGO, + vdd->debug_dir, (void *) vdd, + &calib_volt_debug_fops); } /* Voltage scale and accessory APIs */ @@ -1137,6 +1189,33 @@ struct omap_volt_data *omap_voltage_get_nom_volt(struct voltagedomain *voltdm) } /** + * omap_voltage_calib_reset() - reset the calibrated voltage entries + * @voltdm: voltage domain to reset the entries for + * + * when the calibrated entries are no longer valid, this api allows + * the calibrated voltages to be reset. + */ +int omap_voltage_calib_reset(struct voltagedomain *voltdm) +{ + struct omap_vdd_info *vdd; + struct omap_volt_data *volt_data; + + if (IS_ERR_OR_NULL(voltdm)) { + pr_warning("%s: VDD specified does not exist!\n", __func__); + return -EINVAL; + } + + vdd = container_of(voltdm, struct omap_vdd_info, voltdm); + volt_data = vdd->volt_data; + /* reset the calibrated voltages as 0 */ + while (volt_data->volt_nominal) { + volt_data->volt_calibrated = 0; + volt_data++; + } + return 0; +} + +/** * omap_vp_get_curr_volt() - API to get the current vp voltage. * @voltdm: pointer to the VDD. * diff --git a/arch/arm/plat-omap/Kconfig b/arch/arm/plat-omap/Kconfig index b6333ae..dba7939 100644 --- a/arch/arm/plat-omap/Kconfig +++ b/arch/arm/plat-omap/Kconfig @@ -67,6 +67,23 @@ config OMAP_SMARTREFLEX_CLASS3 Class 3 implementation of Smartreflex employs continuous hardware voltage calibration. +config OMAP_SMARTREFLEX_CLASS1P5 + bool "Class 1.5 mode of Smartreflex Implementation" + depends on OMAP_SMARTREFLEX && TWL4030_CORE + help + Say Y to enable Class 1.5 implementation of Smartreflex + Class 1.5 implementation of Smartreflex employs software controlled + hardware voltage calibration. + +config OMAP_SR_CLASS1P5_RECALIBRATION_DELAY + int "Class 1.5 mode recalibration recalibration delay(ms)" + depends on OMAP_SMARTREFLEX_CLASS1P5 + default 86400000 + help + Setup the recalibration delay in milliseconds. Use 0 for never doing + a recalibration. Defaults to recommended recalibration every 24hrs. + If you do not understand this, use the default. + config OMAP_RESET_CLOCKS bool "Reset unused clocks during boot" depends on ARCH_OMAP diff --git a/arch/arm/plat-omap/include/plat/smartreflex.h b/arch/arm/plat-omap/include/plat/smartreflex.h index 07f35b2..ee5c58f 100644 --- a/arch/arm/plat-omap/include/plat/smartreflex.h +++ b/arch/arm/plat-omap/include/plat/smartreflex.h @@ -167,6 +167,7 @@ struct omap_sr_pmic_data { #define SR_CLASS1 0x1 #define SR_CLASS2 0x2 #define SR_CLASS3 0x3 +#define SR_CLASS1P5 0x4 /** * struct omap_sr_class_data - Smartreflex class driver info @@ -187,7 +188,9 @@ struct omap_sr_pmic_data { struct omap_sr_class_data { int (*enable)(struct voltagedomain *voltdm, struct omap_volt_data *volt_data); - int (*disable)(struct voltagedomain *voltdm, int is_volt_reset); + int (*disable)(struct voltagedomain *voltdm, + struct omap_volt_data *volt_data, + int is_volt_reset); int (*class_init)(struct voltagedomain *voltdm, void *class_priv_data); int (*class_deinit)(struct voltagedomain *voltdm, void *class_priv_data); @@ -235,7 +238,8 @@ struct omap_sr_data { /* Smartreflex module enable/disable interface */ void omap_sr_enable(struct voltagedomain *voltdm, struct omap_volt_data *volt_data); -void omap_sr_disable(struct voltagedomain *voltdm); +void omap_sr_disable(struct voltagedomain *voltdm, + struct omap_volt_data *volt_data); void omap_sr_disable_reset_volt(struct voltagedomain *voltdm); /* API to register the pmic specific data with the smartreflex driver. */ @@ -250,6 +254,7 @@ int sr_configure_minmax(struct voltagedomain *voltdm); /* API to register the smartreflex class driver with the smartreflex driver */ int sr_register_class(struct omap_sr_class_data *class_data); +bool is_sr_enabled(struct voltagedomain *voltdm); #else static inline void omap_sr_enable(struct voltagedomain *voltdm) {} static inline void omap_sr_disable(struct voltagedomain *voltdm) {} @@ -264,5 +269,9 @@ static inline void omap_sr_disable_reset_volt( struct voltagedomain *voltdm) {} static inline void omap_sr_register_pmic( struct omap_sr_pmic_data *pmic_data) {} +static inline bool is_sr_enabled(struct voltagedomain *voltdm) +{ + return false; +} #endif #endif diff --git a/arch/arm/plat-omap/include/plat/voltage.h b/arch/arm/plat-omap/include/plat/voltage.h index 816a785..a11b7c3 100644 --- a/arch/arm/plat-omap/include/plat/voltage.h +++ b/arch/arm/plat-omap/include/plat/voltage.h @@ -58,6 +58,8 @@ #define OMAP4430_VDD_CORE_OPP50_UV 930000 #define OMAP4430_VDD_CORE_OPP100_UV 1100000 +#define OMAP3PLUS_DYNAMIC_NOMINAL_MARGIN_UV 50000 + /** * struct voltagedomain - omap voltage domain global structure. * @name: Name of the voltage domain which can be used as a unique @@ -81,6 +83,8 @@ struct voltagedomain { */ struct omap_volt_data { u32 volt_nominal; + u32 volt_calibrated; + u32 volt_dynamic_nominal; u32 sr_efuse_offs; u8 sr_errminlimit; u8 vp_errgain; @@ -127,6 +131,7 @@ struct omap_volt_data *omap_voltage_get_nom_volt(struct voltagedomain *voltdm); bool omap_vp_is_transdone(struct voltagedomain *voltdm); bool omap_vp_clear_transdone(struct voltagedomain *voltdm); struct dentry *omap_voltage_get_dbgdir(struct voltagedomain *voltdm); +int omap_voltage_calib_reset(struct voltagedomain *voltdm); #ifdef CONFIG_PM int omap_voltage_register_pmic(struct voltagedomain *voltdm, struct omap_volt_pmic_info *pmic_info); @@ -160,7 +165,23 @@ static inline unsigned long omap_get_operation_voltage( { if (IS_ERR_OR_NULL(vdata)) return 0; - return vdata->volt_nominal; + return (vdata->volt_calibrated) ? vdata->volt_calibrated : + (vdata->volt_dynamic_nominal) ? vdata->volt_dynamic_nominal : + vdata->volt_nominal; } +/* what is my dynamic nominal? */ +static inline unsigned long omap_get_dyn_nominal(struct omap_volt_data *vdata) +{ + if (IS_ERR_OR_NULL(vdata)) + return 0; + if (vdata->volt_calibrated) { + unsigned long v = vdata->volt_calibrated + + OMAP3PLUS_DYNAMIC_NOMINAL_MARGIN_UV; + if (v > vdata->volt_nominal) + return vdata->volt_nominal; + return v; + } + return vdata->volt_nominal; +} #endif