From 190f287f035885b34ff08b7e4c88d6c9c32d06d6 Mon Sep 17 00:00:00 2001 From: Nishanth Menon Date: Fri, 4 Mar 2011 07:14:24 +0530 Subject: [PATCH 01/14] OMAP3: hwmod: add SmartReflex IRQs OMAP3 SmartReflex IRQs in hwmod structures with the same naming as present in OMAP4. Without these IRQs being registered, SmartReflex driver will be unable to get the IRQ numbers to handle notifications Change-Id: Iaba431b3d4a5c2e4ce64e3aa54f0148058d6662f Signed-off-by: Nishanth Menon --- arch/arm/mach-omap2/omap_hwmod_3xxx_data.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/arch/arm/mach-omap2/omap_hwmod_3xxx_data.c b/arch/arm/mach-omap2/omap_hwmod_3xxx_data.c index 03a5c65b68f..b3277762377 100644 --- a/arch/arm/mach-omap2/omap_hwmod_3xxx_data.c +++ b/arch/arm/mach-omap2/omap_hwmod_3xxx_data.c @@ -401,6 +401,15 @@ static struct omap_hwmod_ocp_if omap3_l4_core__i2c3 = { .user = OCP_USER_MPU | OCP_USER_SDMA, }; + +static struct omap_hwmod_irq_info omap3_smartreflex_mpu_irqs[] = { + {.name = "sr1_irq", .irq = 18}, +}; + +static struct omap_hwmod_irq_info omap3_smartreflex_core_irqs[] = { + {.name = "sr2_irq", .irq = 19}, +}; + /* L4 CORE -> SR1 interface */ static struct omap_hwmod_addr_space omap3_sr1_addr_space[] = { { @@ -2975,6 +2984,8 @@ static struct omap_hwmod omap34xx_sr1_hwmod = { .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430ES2 | CHIP_IS_OMAP3430ES3_0 | CHIP_IS_OMAP3430ES3_1), + .mpu_irqs = omap3_smartreflex_mpu_irqs, + .mpu_irqs_cnt = ARRAY_SIZE(omap3_smartreflex_mpu_irqs), .flags = HWMOD_SET_DEFAULT_CLOCKACT, }; @@ -2995,6 +3006,8 @@ static struct omap_hwmod omap36xx_sr1_hwmod = { .slaves_cnt = ARRAY_SIZE(omap3_sr1_slaves), .dev_attr = &sr1_dev_attr, .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3630ES1), + .mpu_irqs = omap3_smartreflex_mpu_irqs, + .mpu_irqs_cnt = ARRAY_SIZE(omap3_smartreflex_mpu_irqs), }; /* SR2 */ @@ -3025,6 +3038,8 @@ static struct omap_hwmod omap34xx_sr2_hwmod = { .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430ES2 | CHIP_IS_OMAP3430ES3_0 | CHIP_IS_OMAP3430ES3_1), + .mpu_irqs = omap3_smartreflex_core_irqs, + .mpu_irqs_cnt = ARRAY_SIZE(omap3_smartreflex_core_irqs), .flags = HWMOD_SET_DEFAULT_CLOCKACT, }; @@ -3045,6 +3060,8 @@ static struct omap_hwmod omap36xx_sr2_hwmod = { .slaves_cnt = ARRAY_SIZE(omap3_sr2_slaves), .dev_attr = &sr2_dev_attr, .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3630ES1), + .mpu_irqs = omap3_smartreflex_core_irqs, + .mpu_irqs_cnt = ARRAY_SIZE(omap3_smartreflex_core_irqs), }; /* From d3388088b3e0e96df70bc726ce875085b801cf8c Mon Sep 17 00:00:00 2001 From: Nishanth Menon Date: Sun, 13 Feb 2011 21:27:30 +0530 Subject: [PATCH 02/14] OMAP3+: voltage: use volt_data pointer instead values Voltage values can get confusing in meaning with various SmartReflex classes being active. Depending on the class used, the actual voltage selected might be a variant. For example: With SmartReflex AVS class 3: a) If we don't have SR enabled, we will go to volt_nominal. b) If have SR enabled, we go to volt_nominal, then enable SR and expect it to adjust voltage. We don't really care about the resultant voltage. Essentially, when we ask voltage layer to scale to voltage x for OPP y, it always means x is the nominal voltage for OPP y. Now, once we introduce SmartReflex AVS class 1.5: a) If you are booting for the first time OR if you never enabled SR before, we always go to volt_nominal. b) If you enable SR and the OPP is calibrated, we will not enable SR again when that OPP is accessed anymore, but when we set voltage for an OPP, the voltage achieved will be volt_calibrated. c) If recalibration timeout triggers or SR is disabled after a calibration, the calibrated values are not valid anymore, at this point, setting the voltage will mean volt_dynamic_nominal. So, depending on which state the system is at, voltage for that OPP we are setting has not 1 single value anymore, but 3 possible valid values. For upper layers(DVFS/cpufreq OMAP SoC layers) to use voltage values, it will need to know which type of voltage AVS strategy is being used and the corresponding system state from voltage layer perspective. This would replicate the role of voltage layer, SmartReflex AVS in the upper layers and make the corresponding implementations complex. Since each voltage domain contains a set of volt_data structs representing a voltage point that is supported for that domain, volt_data is a more accurate representation of the voltage point we are interested in going to, and the actual translation of this voltage point to the voltage value is done inside the voltage layer. Doing this allows the users of the voltage layer to be blissfully ignorant of any complexity of the underneath layers and simplify the implementation of dependent layers. As part of this change, omap_voltage_get_nom_volt has been renamed to omap_voltage_get_curr_vdata to better represent it's behavior. Change-Id: Idc171b6c64224c017280e0b29eb4ffb54f702194 Signed-off-by: Nishanth Menon --- arch/arm/mach-omap2/dvfs.c | 21 +++++++++++++--- arch/arm/mach-omap2/pm.c | 6 +++-- arch/arm/mach-omap2/prm44xx.c | 1 + arch/arm/mach-omap2/smartreflex-class3.c | 6 +++-- arch/arm/mach-omap2/vc.c | 9 ++++--- arch/arm/mach-omap2/vc.h | 3 ++- arch/arm/mach-omap2/voltage.c | 32 +++++++++++++++--------- arch/arm/mach-omap2/voltage.h | 20 ++++++++++++--- arch/arm/mach-omap2/vp.c | 19 +++++++++++--- arch/arm/mach-omap2/vp.h | 2 +- 10 files changed, 87 insertions(+), 32 deletions(-) diff --git a/arch/arm/mach-omap2/dvfs.c b/arch/arm/mach-omap2/dvfs.c index 953ed9b51a4..4659e928987 100644 --- a/arch/arm/mach-omap2/dvfs.c +++ b/arch/arm/mach-omap2/dvfs.c @@ -729,6 +729,8 @@ static int _dvfs_scale(struct device *req_dev, struct device *target_dev, int ret = 0; struct voltagedomain *voltdm; struct omap_vdd_info *vdd; + struct omap_volt_data *new_vdata; + struct omap_volt_data *curr_vdata; voltdm = tdvfs_info->voltdm; if (IS_ERR_OR_NULL(voltdm)) { @@ -741,9 +743,22 @@ static int _dvfs_scale(struct device *req_dev, struct device *target_dev, node = plist_last(&tdvfs_info->vdd_user_list); new_volt = node->prio; + new_vdata = omap_voltage_get_voltdata(voltdm, new_volt); + if (IS_ERR_OR_NULL(new_vdata)) { + pr_err("%s:%s: Bad New voltage data for %ld\n", + __func__, voltdm->name, new_volt); + return PTR_ERR(new_vdata); + } + new_volt = omap_get_operation_voltage(new_vdata); + curr_vdata = omap_voltage_get_curr_vdata(voltdm); + if (IS_ERR_OR_NULL(curr_vdata)) { + pr_err("%s:%s: Bad Current voltage data\n", + __func__, voltdm->name); + return PTR_ERR(curr_vdata); + } curr_volt = omap_vp_get_curr_volt(voltdm); if (!curr_volt) - curr_volt = omap_voltage_get_nom_volt(voltdm); + curr_volt = omap_get_operation_voltage(curr_vdata); /* Disable smartreflex module across voltage and frequency scaling */ omap_sr_disable(voltdm); @@ -760,7 +775,7 @@ static int _dvfs_scale(struct device *req_dev, struct device *target_dev, goto fail; } - ret = voltdm_scale(voltdm, new_volt); + ret = voltdm_scale(voltdm, new_vdata); if (ret) { dev_err(target_dev, "%s: Unable to scale the %s to %ld volt\n", @@ -817,7 +832,7 @@ static int _dvfs_scale(struct device *req_dev, struct device *target_dev, goto fail; if (DVFS_VOLT_SCALE_DOWN == volt_scale_dir) { - voltdm_scale(voltdm, new_volt); + voltdm_scale(voltdm, new_vdata); _dep_scale_domains(target_dev, vdd); } diff --git a/arch/arm/mach-omap2/pm.c b/arch/arm/mach-omap2/pm.c index 9cba64975b7..1afb2f6f968 100644 --- a/arch/arm/mach-omap2/pm.c +++ b/arch/arm/mach-omap2/pm.c @@ -370,7 +370,8 @@ static int __init omap2_set_init_voltage(char *vdd_name, char *clk_name, */ if (freq_cur < freq_valid) { - ret = voltdm_scale(voltdm, bootup_volt); + ret = voltdm_scale(voltdm, + omap_voltage_get_voltdata(voltdm, bootup_volt)); if (ret) { pr_err("%s: Fail set voltage-%s(f=%ld v=%ld)on vdd%s\n", __func__, vdd_name, freq_valid, @@ -391,7 +392,8 @@ static int __init omap2_set_init_voltage(char *vdd_name, char *clk_name, } if (freq_cur >= freq_valid) { - ret = voltdm_scale(voltdm, bootup_volt); + ret = voltdm_scale(voltdm, + omap_voltage_get_voltdata(voltdm, bootup_volt)); if (ret) { pr_err("%s: Fail set voltage-%s(f=%ld v=%ld)on vdd%s\n", __func__, clk_name, freq_valid, diff --git a/arch/arm/mach-omap2/prm44xx.c b/arch/arm/mach-omap2/prm44xx.c index ffc79ee4793..6f011e070b8 100644 --- a/arch/arm/mach-omap2/prm44xx.c +++ b/arch/arm/mach-omap2/prm44xx.c @@ -21,6 +21,7 @@ #include #include +#include "voltage.h" #include "vp.h" #include "prm44xx.h" #include "prm-regbits-44xx.h" diff --git a/arch/arm/mach-omap2/smartreflex-class3.c b/arch/arm/mach-omap2/smartreflex-class3.c index 5bc7ef04e20..44be57a9ede 100644 --- a/arch/arm/mach-omap2/smartreflex-class3.c +++ b/arch/arm/mach-omap2/smartreflex-class3.c @@ -15,13 +15,15 @@ static int sr_class3_enable(struct voltagedomain *voltdm) { - unsigned long volt = omap_voltage_get_nom_volt(voltdm); + struct omap_volt_data *v = omap_voltage_get_curr_vdata(voltdm); + unsigned long volt; - if (!volt) { + if (IS_ERR_OR_NULL(v)) { pr_warning("%s: Curr voltage unknown. Cannot enable sr_%s\n", __func__, voltdm->name); return -ENODATA; } + volt = omap_get_operation_voltage(v); omap_vp_enable(voltdm); return sr_enable(voltdm, volt); diff --git a/arch/arm/mach-omap2/vc.c b/arch/arm/mach-omap2/vc.c index cbf9a3b083d..162ee9f6aa8 100644 --- a/arch/arm/mach-omap2/vc.c +++ b/arch/arm/mach-omap2/vc.c @@ -197,6 +197,7 @@ int omap_vc_set_auto_trans(struct voltagedomain *voltdm, u8 flag) void omap_vc_post_scale(struct voltagedomain *voltdm, unsigned long target_volt, + struct omap_volt_data *target_vdata, u8 target_vsel, u8 current_vsel) { struct omap_vc_channel *vc; @@ -221,7 +222,7 @@ void omap_vc_post_scale(struct voltagedomain *voltdm, voltdm->pmic->slew_rate) + 2; udelay(smps_delay); - voltdm->curr_volt = target_volt; + voltdm->curr_volt = target_vdata; /* Set up the on voltage for wakeup from lp and OFF */ on_vsel = voltdm->pmic->uv_to_vsel(target_volt); @@ -281,11 +282,12 @@ static int omap_vc_bypass_send_value(struct voltagedomain *voltdm, /* vc_bypass_scale_voltage - VC bypass method of voltage scaling */ int omap_vc_bypass_scale_voltage(struct voltagedomain *voltdm, - unsigned long target_volt) + struct omap_volt_data *target_v) { struct omap_vc_channel *vc; u8 target_vsel, current_vsel; int ret; + unsigned long target_volt = omap_get_operation_voltage(target_v); if (IS_ERR_OR_NULL(voltdm)) { pr_err("%s bad voldm\n", __func__); @@ -308,7 +310,8 @@ int omap_vc_bypass_scale_voltage(struct voltagedomain *voltdm, if (ret) return ret; - omap_vc_post_scale(voltdm, target_volt, target_vsel, current_vsel); + omap_vc_post_scale(voltdm, target_volt, target_v, target_vsel, + current_vsel); return 0; } diff --git a/arch/arm/mach-omap2/vc.h b/arch/arm/mach-omap2/vc.h index a5f25fa6706..473375f44ef 100644 --- a/arch/arm/mach-omap2/vc.h +++ b/arch/arm/mach-omap2/vc.h @@ -144,6 +144,7 @@ int omap_vc_pre_scale(struct voltagedomain *voltdm, u8 *target_vsel, u8 *current_vsel); void omap_vc_post_scale(struct voltagedomain *voltdm, unsigned long target_volt, + struct omap_volt_data *target_vdata, u8 target_vsel, u8 current_vsel); /* Auto transition flags for users */ @@ -155,7 +156,7 @@ void omap_vc_post_scale(struct voltagedomain *voltdm, #define OMAP_VC_CHANNEL_AUTO_TRANSITION_UNSUPPORTED 0xff int omap_vc_set_auto_trans(struct voltagedomain *voltdm, u8 flag); int omap_vc_bypass_scale_voltage(struct voltagedomain *voltdm, - unsigned long target_volt); + struct omap_volt_data *target_volt); int omap_vc_bypass_send_i2c_msg(struct voltagedomain *voltdm, u8 slave_addr, u8 reg_addr, u8 data); #endif diff --git a/arch/arm/mach-omap2/voltage.c b/arch/arm/mach-omap2/voltage.c index 57a153c8da2..60fcdaeea7c 100644 --- a/arch/arm/mach-omap2/voltage.c +++ b/arch/arm/mach-omap2/voltage.c @@ -88,17 +88,16 @@ static int __init omap_vdd_data_configure(struct voltagedomain *voltdm) /* Public functions */ /** - * omap_voltage_get_nom_volt() - Gets the current non-auto-compensated voltage + * omap_voltage_get_curr_vdata() - Gets the current voltage data * @voltdm: pointer to the VDD for which current voltage info is needed * - * API to get the current non-auto-compensated voltage for a VDD. - * Returns 0 in case of error else returns the current voltage for the VDD. + * API to get the current voltage data pointer for a VDD, returns NULL on error */ -unsigned long omap_voltage_get_nom_volt(struct voltagedomain *voltdm) +struct omap_volt_data *omap_voltage_get_curr_vdata(struct voltagedomain *voltdm) { if (!voltdm || IS_ERR(voltdm)) { pr_warning("%s: VDD specified does not exist!\n", __func__); - return 0; + return NULL; } return voltdm->curr_volt; @@ -113,10 +112,11 @@ unsigned long omap_voltage_get_nom_volt(struct voltagedomain *voltdm) * for a particular voltage domain during DVFS. */ int voltdm_scale(struct voltagedomain *voltdm, - unsigned long target_volt) + struct omap_volt_data *target_v) { int ret = 0; struct omap_voltage_notifier notify; + unsigned long target_volt = omap_get_operation_voltage(target_v); if (!voltdm || IS_ERR(voltdm)) { pr_warning("%s: VDD specified does not exist!\n", __func__); @@ -145,14 +145,15 @@ int voltdm_scale(struct voltagedomain *voltdm, } if (!ret) { - ret = voltdm->scale(voltdm, target_volt); + ret = voltdm->scale(voltdm, target_v); if (ret) pr_err("%s: voltage scale failed for vdd%s: %d\n", __func__, voltdm->name, ret); if (voltdm->abb) { - ret = omap_ldo_abb_post_scale(voltdm, - voltdm->curr_volt); + unsigned long cv; + cv = omap_get_operation_voltage(voltdm->curr_volt); + ret = omap_ldo_abb_post_scale(voltdm, cv); if (ret) pr_err("%s: ABB postscale fail for vdd%s:%d\n", __func__, voltdm->name, ret); @@ -179,14 +180,14 @@ int voltdm_scale(struct voltagedomain *voltdm, */ void voltdm_reset(struct voltagedomain *voltdm) { - unsigned long target_volt; + struct omap_volt_data *target_volt; if (!voltdm || IS_ERR(voltdm)) { pr_warning("%s: VDD specified does not exist!\n", __func__); return; } - target_volt = omap_voltage_get_nom_volt(voltdm); + target_volt = omap_voltage_get_curr_vdata(voltdm); if (!target_volt) { pr_err("%s: unable to find current voltage for vdd_%s\n", __func__, voltdm->name); @@ -339,13 +340,20 @@ DEFINE_SIMPLE_ATTRIBUTE(vp_volt_debug_fops, vp_volt_debug_get, NULL, "%llu\n"); static int nom_volt_debug_get(void *data, u64 *val) { struct voltagedomain *voltdm = (struct voltagedomain *) data; + struct omap_volt_data *vdata; if (!voltdm) { pr_warning("Wrong paramater passed\n"); return -EINVAL; } - *val = omap_voltage_get_nom_volt(voltdm); + vdata = omap_voltage_get_curr_vdata(voltdm); + if (IS_ERR_OR_NULL(vdata)) { + pr_warning("%s: unable to get volt for vdd_%s\n", + __func__, voltdm->name); + return -ENODEV; + } + *val = vdata->volt_nominal; return 0; } diff --git a/arch/arm/mach-omap2/voltage.h b/arch/arm/mach-omap2/voltage.h index 0a9f529096f..29319831ffd 100644 --- a/arch/arm/mach-omap2/voltage.h +++ b/arch/arm/mach-omap2/voltage.h @@ -17,6 +17,8 @@ #include #include +struct omap_volt_data; + #include "vc.h" #include "vp.h" #include "ldo.h" @@ -90,8 +92,8 @@ struct voltagedomain { } sys_clk; int (*scale) (struct voltagedomain *voltdm, - unsigned long target_volt); - u32 curr_volt; + struct omap_volt_data *target_volt); + struct omap_volt_data *curr_volt; struct omap_vdd_info *vdd; struct srcu_notifier_head change_notify_list; @@ -266,7 +268,7 @@ void omap_voltage_get_volttable(struct voltagedomain *voltdm, struct omap_volt_data **volt_data); struct omap_volt_data *omap_voltage_get_voltdata(struct voltagedomain *voltdm, unsigned long volt); -unsigned long omap_voltage_get_nom_volt(struct voltagedomain *voltdm); +struct omap_volt_data *omap_voltage_get_curr_vdata(struct voltagedomain *voldm); #ifdef CONFIG_PM int omap_voltage_register_pmic(struct voltagedomain *voltdm, struct omap_voltdm_pmic *pmic); @@ -299,7 +301,8 @@ int voltdm_for_each(int (*fn)(struct voltagedomain *voltdm, void *user), int voltdm_for_each_pwrdm(struct voltagedomain *voltdm, int (*fn)(struct voltagedomain *voltdm, struct powerdomain *pwrdm)); -int voltdm_scale(struct voltagedomain *voltdm, unsigned long target_volt); +int voltdm_scale(struct voltagedomain *voltdm, + struct omap_volt_data *target_volt); void voltdm_reset(struct voltagedomain *voltdm); static inline int voltdm_register_notifier(struct voltagedomain *voltdm, @@ -314,4 +317,13 @@ static inline int voltdm_unregister_notifier(struct voltagedomain *voltdm, return srcu_notifier_chain_unregister(&voltdm->change_notify_list, nb); } +/* convert volt data to the voltage for the voltage data */ +static inline unsigned long omap_get_operation_voltage( + struct omap_volt_data *vdata) +{ + if (!vdata) + return 0; + return vdata->volt_nominal; +} + #endif diff --git a/arch/arm/mach-omap2/vp.c b/arch/arm/mach-omap2/vp.c index d3d97915592..7ccf0a39f21 100644 --- a/arch/arm/mach-omap2/vp.c +++ b/arch/arm/mach-omap2/vp.c @@ -13,11 +13,17 @@ 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; - uvdc = omap_voltage_get_nom_volt(voltdm); + 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); @@ -106,8 +112,11 @@ int omap_vp_update_errorgain(struct voltagedomain *voltdm, /* Get volt_data corresponding to target_volt */ volt_data = omap_voltage_get_voltdata(voltdm, target_volt); - if (IS_ERR(volt_data)) + if (IS_ERR(volt_data)) { + pr_err("%s: vdm %s no voltage data for %ld\n", __func__, + voltdm->name, target_volt); return -EINVAL; + } /* Setting vp errorgain based on the voltage */ voltdm->rmw(voltdm->vp->common->vpconfig_errorgain_mask, @@ -143,12 +152,13 @@ static u8 __vp_recover_count = _MAX_RETRIES_BEFORE_RECOVER; /* VP force update method of voltage scaling */ int omap_vp_forceupdate_scale(struct voltagedomain *voltdm, - unsigned long target_volt) + struct omap_volt_data *target_v) { struct omap_vp_instance *vp = voltdm->vp; u32 vpconfig; u8 target_vsel, current_vsel; int ret, timeout = 0; + unsigned long target_volt = omap_get_operation_voltage(target_v); /* * Wait for VP idle Typical latency is <2us. Maximum latency is ~100us @@ -220,7 +230,8 @@ int omap_vp_forceupdate_scale(struct voltagedomain *voltdm, __func__, voltdm->name, target_volt, target_vsel, current_vsel); - omap_vc_post_scale(voltdm, target_volt, target_vsel, current_vsel); + omap_vc_post_scale(voltdm, target_volt, target_v, + target_vsel, current_vsel); /* * Disable TransactionDone interrupt , clear all status, clear diff --git a/arch/arm/mach-omap2/vp.h b/arch/arm/mach-omap2/vp.h index e5f65fd7bd5..c3a1e69aafd 100644 --- a/arch/arm/mach-omap2/vp.h +++ b/arch/arm/mach-omap2/vp.h @@ -114,7 +114,7 @@ void omap_vp_enable(struct voltagedomain *voltdm); void omap_vp_disable(struct voltagedomain *voltdm); unsigned long omap_vp_get_curr_volt(struct voltagedomain *voltdm); int omap_vp_forceupdate_scale(struct voltagedomain *voltdm, - unsigned long target_volt); + struct omap_volt_data *target_v); int omap_vp_update_errorgain(struct voltagedomain *voltdm, unsigned long target_volt); From 938771436022c03ff3879d0ca9dd941c0d75d155 Mon Sep 17 00:00:00 2001 From: Nishanth Menon Date: Fri, 18 Feb 2011 10:43:25 +0530 Subject: [PATCH 03/14] OMAP3+: voltage: expose transdone APIs Transdone event in Voltage processor gives us fine grained status on the current status of the voltage communication with the PMIC. Unfortunately, IRQ generation by VP is based on the start of the transmission to VC from VP, not at the end (or the completion of the voltage setting). Hence any users of voltage layer who need to know fine grained information such as confirmation if the voltage is actually send to PMIC, needs to depend on this status. Change-Id: I5fdef6ff9a6d4b90a5f04c3adfb4209c75b1a26e Signed-off-by: Nishanth Menon --- arch/arm/mach-omap2/vp.c | 30 ++++++++++++++++++++++++++++++ arch/arm/mach-omap2/vp.h | 2 ++ 2 files changed, 32 insertions(+) diff --git a/arch/arm/mach-omap2/vp.c b/arch/arm/mach-omap2/vp.c index 7ccf0a39f21..3989cfd70f0 100644 --- a/arch/arm/mach-omap2/vp.c +++ b/arch/arm/mach-omap2/vp.c @@ -105,6 +105,36 @@ void __init omap_vp_init(struct voltagedomain *voltdm) voltdm->write(val, vp->vlimitto); } +/** + * omap_vp_is_transdone() - is voltage transfer done on vp? + * @voltdm: pointer to the VDD which is to be scaled. + * + * VP's transdone bit is the only way to ensure that the transfer + * of the voltage value has actually been send over to the PMIC + * This is hence useful for all users of voltage domain to precisely + * identify once the PMIC voltage has been set by the voltage processor + */ +bool omap_vp_is_transdone(struct voltagedomain *voltdm) +{ + + struct omap_vp_instance *vp = voltdm->vp; + + return vp->common->ops->check_txdone(vp->id) ? true : false; +} + +/** + * omap_vp_clear_transdone() - clear voltage transfer done status on vp + * @voltdm: pointer to the VDD which is to be scaled. + */ +void omap_vp_clear_transdone(struct voltagedomain *voltdm) +{ + struct omap_vp_instance *vp = voltdm->vp; + + vp->common->ops->clear_txdone(vp->id); + + return; +} + int omap_vp_update_errorgain(struct voltagedomain *voltdm, unsigned long target_volt) { diff --git a/arch/arm/mach-omap2/vp.h b/arch/arm/mach-omap2/vp.h index c3a1e69aafd..88381bbee58 100644 --- a/arch/arm/mach-omap2/vp.h +++ b/arch/arm/mach-omap2/vp.h @@ -117,5 +117,7 @@ int omap_vp_forceupdate_scale(struct voltagedomain *voltdm, struct omap_volt_data *target_v); int omap_vp_update_errorgain(struct voltagedomain *voltdm, unsigned long target_volt); +bool omap_vp_is_transdone(struct voltagedomain *voltdm); +void omap_vp_clear_transdone(struct voltagedomain *voltdm); #endif From d16236560b184558833eab092e8cf35cc8df3952 Mon Sep 17 00:00:00 2001 From: Nishanth Menon Date: Wed, 30 Nov 2011 04:27:24 -0600 Subject: [PATCH 04/14] OMAP2+: voltage: update omap_vdd_info documentation Current documentation lacks clarity that the volt_data array is '0' terminated. Change-Id: Id4364511cf9c04ca958c26533f7a95b6c11ca6d3 Reported-by: Todd Poynor Signed-off-by: Nishanth Menon --- arch/arm/mach-omap2/voltage.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/arch/arm/mach-omap2/voltage.h b/arch/arm/mach-omap2/voltage.h index 29319831ffd..ce49e064b19 100644 --- a/arch/arm/mach-omap2/voltage.h +++ b/arch/arm/mach-omap2/voltage.h @@ -254,7 +254,8 @@ struct omap_vdd_dep_info { /** * omap_vdd_info - Per Voltage Domain info * - * @volt_data : voltage table having the distinct voltages supported + * @volt_data : Array ending with a 0 terminator containing the + * voltage table with distinct voltages supported * by the domain and other associated per voltage data. * @dep_vdd_info : Array ending with a 0 terminator for dependency * voltage information. From 0b21c45b4e940b5e04be97da4ce2f331aff9d0b4 Mon Sep 17 00:00:00 2001 From: Nishanth Menon Date: Mon, 14 Feb 2011 12:29:03 +0530 Subject: [PATCH 05/14] OMAP3+: SR: introduce class init,deinit and priv data Certain class drivers such as class 1.5 drivers, will need specific notification that they have to be inited up or deinited independent of smart reflex operation. They also may need private data to be used for operations of their own, provide the same. This allows the class driver to initialize it's internal data structures on a need basis in preparation for the specific domain's autocomp usage. The resultant operation is as follows: when autocomp is set to 1 -> class drivers' init is called The existing enable/disable is still used as before by the SmartReflex core driver to enable disable the class operation. When autocomp is set to 0 -> class drivers' deinit is called, signaling the end of usage for that domain. Change-Id: Ie6a4b163d52af4931a4506ed7a3a6b752173a674 Signed-off-by: Nishanth Menon --- arch/arm/mach-omap2/smartreflex.c | 16 ++++++++++++++++ arch/arm/mach-omap2/smartreflex.h | 6 ++++++ 2 files changed, 22 insertions(+) diff --git a/arch/arm/mach-omap2/smartreflex.c b/arch/arm/mach-omap2/smartreflex.c index 3b28da902ae..823d334b089 100644 --- a/arch/arm/mach-omap2/smartreflex.c +++ b/arch/arm/mach-omap2/smartreflex.c @@ -220,6 +220,15 @@ static void sr_start_vddautocomp(struct omap_sr *sr) /* pause dvfs from interfereing with our operations */ mutex_lock(&omap_dvfs_lock); + + if (sr_class->init && + sr_class->init(sr->voltdm, sr_class->class_priv_data)) { + dev_err(&sr->pdev->dev, + "%s: SRClass initialization failed\n", __func__); + mutex_unlock(&omap_dvfs_lock); + return; + } + if (!sr_class->enable(sr->voltdm)) sr->autocomp_active = true; mutex_unlock(&omap_dvfs_lock); @@ -238,6 +247,13 @@ static void sr_stop_vddautocomp(struct omap_sr *sr) /* Pause dvfs from interfereing with our operations */ mutex_lock(&omap_dvfs_lock); sr_class->disable(sr->voltdm, 1); + if (sr_class->deinit && + sr_class->deinit(sr->voltdm, + sr_class->class_priv_data)) { + dev_err(&sr->pdev->dev, + "%s: SR[%d]Class deinitialization failed\n", + __func__, sr->srid); + } sr->autocomp_active = false; mutex_unlock(&omap_dvfs_lock); } diff --git a/arch/arm/mach-omap2/smartreflex.h b/arch/arm/mach-omap2/smartreflex.h index 5809141171f..dd5b21e3f1a 100644 --- a/arch/arm/mach-omap2/smartreflex.h +++ b/arch/arm/mach-omap2/smartreflex.h @@ -177,6 +177,8 @@ struct omap_smartreflex_dev_attr { * * @enable: API to enable a particular class smaartreflex. * @disable: API to disable a particular class smartreflex. + * @init: API to do class specific initialization (optional) + * @deinit: API to do class specific deinitialization (optional) * @configure: API to configure a particular class smartreflex. * @notify: API to notify the class driver about an event in SR. * Not needed for class3. @@ -184,14 +186,18 @@ struct omap_smartreflex_dev_attr { * @class_type: specify which smartreflex class. * Can be used by the SR driver to take any class * based decisions. + * @class_priv_data: Class specific private data (optional) */ struct omap_sr_class_data { int (*enable)(struct voltagedomain *voltdm); int (*disable)(struct voltagedomain *voltdm, int is_volt_reset); + int (*init)(struct voltagedomain *voltdm, void *class_priv_data); + int (*deinit)(struct voltagedomain *voltdm, void *class_priv_data); int (*configure)(struct voltagedomain *voltdm); int (*notify)(struct voltagedomain *voltdm, u32 status); u8 notify_flags; u8 class_type; + void *class_priv_data; }; /** From 88f295e44d9b263d8094627690278164948f72bd Mon Sep 17 00:00:00 2001 From: Nishanth Menon Date: Mon, 14 Feb 2011 21:27:16 +0530 Subject: [PATCH 06/14] OMAP3+: SR: introduce notifiers flags SmartReflex IP V1 and V2 have different registers and offsets. Currently, we pass the status as is to the class driver. However, since we don't pass the version of the underlying SR hardware to the Class driver, it will not be unable to make consistent sense of the status bits coming over to it. A class driver should be able to function without dependency on the exact IP version it is actually running on. We hence introduce our own translation in software level for a generic notification flag. As part of this change, we will now call the notifier iff we get a match with the notifier flags that the class driver requested. Change-Id: I431e1047b11ca93528fa669b34e5059368471bf6 Signed-off-by: Nishanth Menon --- arch/arm/mach-omap2/smartreflex.c | 81 +++++++++++++++++++++++++++++-- arch/arm/mach-omap2/smartreflex.h | 6 +++ 2 files changed, 84 insertions(+), 3 deletions(-) diff --git a/arch/arm/mach-omap2/smartreflex.c b/arch/arm/mach-omap2/smartreflex.c index 823d334b089..3796395e02e 100644 --- a/arch/arm/mach-omap2/smartreflex.c +++ b/arch/arm/mach-omap2/smartreflex.c @@ -121,27 +121,102 @@ static struct omap_sr *_sr_lookup(struct voltagedomain *voltdm) return ERR_PTR(-ENODATA); } +static inline u32 notifier_to_irqen_v1(u8 notify_flags) +{ + u32 val; + + val = (notify_flags & SR_NOTIFY_MCUACCUM) ? + ERRCONFIG_MCUACCUMINTEN : 0; + val |= (notify_flags & SR_NOTIFY_MCUVALID) ? + ERRCONFIG_MCUVALIDINTEN : 0; + val |= (notify_flags & SR_NOTIFY_MCUBOUND) ? + ERRCONFIG_MCUBOUNDINTEN : 0; + val |= (notify_flags & SR_NOTIFY_MCUDISACK) ? + ERRCONFIG_MCUDISACKINTEN : 0; + + return val; +} + +static inline u32 notifier_to_irqen_v2(u8 notify_flags) +{ + u32 val; + + val = (notify_flags & SR_NOTIFY_MCUACCUM) ? + IRQENABLE_MCUACCUMINT : 0; + val |= (notify_flags & SR_NOTIFY_MCUVALID) ? + IRQENABLE_MCUVALIDINT : 0; + val |= (notify_flags & SR_NOTIFY_MCUBOUND) ? + IRQENABLE_MCUBOUNDSINT : 0; + val |= (notify_flags & SR_NOTIFY_MCUDISACK) ? + IRQENABLE_MCUDISABLEACKINT : 0; + + return val; +} + +static inline u8 irqstat_to_notifier_v1(u32 status) +{ + u8 val; + + val = (status & ERRCONFIG_MCUACCUMINTST) ? + SR_NOTIFY_MCUACCUM : 0; + val |= (status & ERRCONFIG_MCUVALIDINTEN) ? + SR_NOTIFY_MCUVALID : 0; + val |= (status & ERRCONFIG_MCUBOUNDINTEN) ? + SR_NOTIFY_MCUBOUND : 0; + val |= (status & ERRCONFIG_MCUDISACKINTEN) ? + SR_NOTIFY_MCUDISACK : 0; + + return val; +} + +static inline u8 irqstat_to_notifier_v2(u32 status) +{ + u8 val; + + val = (status & IRQENABLE_MCUACCUMINT) ? + SR_NOTIFY_MCUACCUM : 0; + val |= (status & IRQENABLE_MCUVALIDINT) ? + SR_NOTIFY_MCUVALID : 0; + val |= (status & IRQENABLE_MCUBOUNDSINT) ? + SR_NOTIFY_MCUBOUND : 0; + val |= (status & IRQENABLE_MCUDISABLEACKINT) ? + SR_NOTIFY_MCUDISACK : 0; + + return val; +} + + static irqreturn_t sr_interrupt(int irq, void *data) { struct omap_sr *sr_info = (struct omap_sr *)data; u32 status = 0; + u32 value = 0; if (sr_info->ip_type == SR_TYPE_V1) { + /* Status bits are one bit before enable bits in v1 */ + value = notifier_to_irqen_v1(sr_class->notify_flags) >> 1; + /* Read the status bits */ status = sr_read_reg(sr_info, ERRCONFIG_V1); + status &= value; /* Clear them by writing back */ - sr_write_reg(sr_info, ERRCONFIG_V1, status); + sr_modify_reg(sr_info, ERRCONFIG_V1, value, status); + + value = irqstat_to_notifier_v1(status); } else if (sr_info->ip_type == SR_TYPE_V2) { + value = notifier_to_irqen_v2(sr_class->notify_flags); /* Read the status bits */ - sr_read_reg(sr_info, IRQSTATUS); + status = sr_read_reg(sr_info, IRQSTATUS); + status &= value; /* Clear them by writing back */ sr_write_reg(sr_info, IRQSTATUS, status); + value = irqstat_to_notifier_v2(status); } if (sr_class->notify) - sr_class->notify(sr_info->voltdm, status); + sr_class->notify(sr_info->voltdm, value); return IRQ_HANDLED; } diff --git a/arch/arm/mach-omap2/smartreflex.h b/arch/arm/mach-omap2/smartreflex.h index dd5b21e3f1a..da00f8c4ba9 100644 --- a/arch/arm/mach-omap2/smartreflex.h +++ b/arch/arm/mach-omap2/smartreflex.h @@ -142,6 +142,12 @@ #define OMAP3430_SR_ERRWEIGHT 0x04 #define OMAP3430_SR_ERRMAXLIMIT 0x02 +/* Smart reflex notifiers for class drivers to use */ +#define SR_NOTIFY_MCUDISACK BIT(3) +#define SR_NOTIFY_MCUBOUND BIT(2) +#define SR_NOTIFY_MCUVALID BIT(1) +#define SR_NOTIFY_MCUACCUM BIT(0) + /** * struct omap_sr_pmic_data - Strucutre to be populated by pmic code to pass * pmic specific info to smartreflex driver From fdfbe6430408f33d09fd77e2162ae61ee52a3c73 Mon Sep 17 00:00:00 2001 From: Nishanth Menon Date: Mon, 14 Feb 2011 21:42:34 +0530 Subject: [PATCH 07/14] OMAP3+: SR: introduce notifier_control We need some mechanism from class drivers to control when notifiers should be triggered and when not, currently we have none, which makes Class driver usage of the interrupt events almost impossible. We also ensure that disable/enable or irq is always guarenteed to be paired. The need to do this is because of the mixture of interrupt based and polling based operations that is implemented as the most optimal strategy for various SmartReflex AVS class Introduce an SmartReflex driver API for doing the same. This is useful for SmartReflex AVS class 1.5 or 2 drivers. Change-Id: Id5187a7631d4743c928ad20da31919e6fd71ce2e Signed-off-by: Nishanth Menon --- arch/arm/mach-omap2/smartreflex.c | 65 +++++++++++++++++++++++++++++++ arch/arm/mach-omap2/smartreflex.h | 8 ++++ 2 files changed, 73 insertions(+) diff --git a/arch/arm/mach-omap2/smartreflex.c b/arch/arm/mach-omap2/smartreflex.c index 3796395e02e..712bf986ef3 100644 --- a/arch/arm/mach-omap2/smartreflex.c +++ b/arch/arm/mach-omap2/smartreflex.c @@ -50,6 +50,7 @@ struct omap_sr { u32 senp_mod; u32 senn_mod; unsigned int irq; + bool irq_enabled; void __iomem *base; struct platform_device *pdev; struct list_head node; @@ -772,6 +773,70 @@ void sr_disable(struct voltagedomain *voltdm) pm_runtime_put_sync_suspend(&sr->pdev->dev); } +/** + * sr_notifier_control() - control the notifier mechanism + * @voltdm: VDD pointer to which the SR module to be configured belongs to. + * @enable: true to enable notifiers and false to disable the same + * + * SR modules allow an MCU interrupt mechanism that vary based on the IP + * revision, we allow the system to generate interrupt if the class driver + * has capability to handle the same. it is upto the class driver to ensure + * the proper sequencing and handling for a clean implementation. returns + * 0 if all goes fine, else returns failure results + */ +int sr_notifier_control(struct voltagedomain *voltdm, bool enable) +{ + struct omap_sr *sr = _sr_lookup(voltdm); + u32 value = 0; + + if (!sr) { + pr_warning("%s: sr corresponding to domain not found\n", + __func__); + return -EINVAL; + } + if (!sr->autocomp_active) + return -EINVAL; + + /* If I could never register an ISR, why bother?? */ + if (!(sr_class && sr_class->notify && sr_class->notify_flags && + sr->irq)) { + dev_warn(&sr->pdev->dev, + "%s: unable to setup IRQ without handling mechanism\n", + __func__); + return -EINVAL; + } + + switch (sr->ip_type) { + case SR_TYPE_V1: + value = notifier_to_irqen_v1(sr_class->notify_flags); + sr_modify_reg(sr, ERRCONFIG_V1, value, + (enable) ? value : 0); + break; + case SR_TYPE_V2: + value = notifier_to_irqen_v2(sr_class->notify_flags); + sr_write_reg(sr, (enable) ? IRQENABLE_SET : IRQENABLE_CLR, + value); + break; + default: + dev_warn(&sr->pdev->dev, "%s: unknown type of sr??\n", + __func__); + return -EINVAL; + } + + if (!enable) + sr_write_reg(sr, IRQSTATUS, value); + + if (enable != sr->irq_enabled) { + if (enable) + enable_irq(sr->irq); + else + disable_irq(sr->irq); + sr->irq_enabled = enable; + } + + return 0; +} + /** * sr_register_class() - API to register a smartreflex class parameters. * @class_data: The structure containing various sr class specific data. diff --git a/arch/arm/mach-omap2/smartreflex.h b/arch/arm/mach-omap2/smartreflex.h index da00f8c4ba9..8660049ba2d 100644 --- a/arch/arm/mach-omap2/smartreflex.h +++ b/arch/arm/mach-omap2/smartreflex.h @@ -251,6 +251,7 @@ void omap_sr_register_pmic(struct omap_sr_pmic_data *pmic_data); /* Smartreflex driver hooks to be called from Smartreflex class driver */ int sr_enable(struct voltagedomain *voltdm, unsigned long volt); void sr_disable(struct voltagedomain *voltdm); +int sr_notifier_control(struct voltagedomain *voltdm, bool enable); int sr_configure_errgen(struct voltagedomain *voltdm); int sr_disable_errgen(struct voltagedomain *voltdm); int sr_configure_minmax(struct voltagedomain *voltdm); @@ -260,6 +261,13 @@ int sr_register_class(struct omap_sr_class_data *class_data); #else static inline void omap_sr_enable(struct voltagedomain *voltdm) {} static inline void omap_sr_disable(struct voltagedomain *voltdm) {} + +static inline int sr_notifier_control(struct voltagedomain *voltdm, + bool enable) +{ + return -EINVAL; +} + static inline void omap_sr_disable_reset_volt( struct voltagedomain *voltdm) {} static inline void omap_sr_register_pmic( From 5f89eefc8269bdf936efcfcacdfa3c6cdf063f69 Mon Sep 17 00:00:00 2001 From: Nishanth Menon Date: Mon, 14 Feb 2011 21:48:24 +0530 Subject: [PATCH 08/14] OMAP3+: SR: disable spamming interrupts At times with bad SR configurations, especially during silicon bring-ups, we could get continuous spurious interrupts which end up hanging the platform in the form of an ISR call for status bits that are automatically enabled by the hardware without any software clearing option. If we detect scenarios where ISR was called without the corresponding notification bit being set, instead of hanging up the system, we will disable interrupt after noting the event in the system log to try and keep system sanity and allow developer to debug and fix the condition. Change-Id: I2eeb25ef18e4465f41b2462d13bfd60e1b1e8d7c Signed-off-by: Nishanth Menon --- arch/arm/mach-omap2/smartreflex.c | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/arch/arm/mach-omap2/smartreflex.c b/arch/arm/mach-omap2/smartreflex.c index 712bf986ef3..be05e953941 100644 --- a/arch/arm/mach-omap2/smartreflex.c +++ b/arch/arm/mach-omap2/smartreflex.c @@ -216,8 +216,25 @@ static irqreturn_t sr_interrupt(int irq, void *data) value = irqstat_to_notifier_v2(status); } - if (sr_class->notify) - sr_class->notify(sr_info->voltdm, value); + /* Attempt some resemblance of recovery! */ + if (!value) { + dev_err(&sr_info->pdev->dev, "%s: Spurious interrupt!" + "status = 0x%08x. Disabling to prevent spamming!!\n", + __func__, status); + disable_irq_nosync(sr_info->irq); + sr_info->irq_enabled = false; + } else { + /* If the caller reports inability to handle, disable as well */ + if (sr_class->notify && + sr_class->notify(sr_info->voltdm, value)) { + dev_err(&sr_info->pdev->dev, "%s: Callback cant handle!" + "status=0x%08x. Disabling to prevent spam!!\n", + __func__, status); + disable_irq_nosync(sr_info->irq); + sr_info->irq_enabled = false; + } + + } return IRQ_HANDLED; } From 1cab51e11bd76e06e5ae4e5c5aa179f722a327ab Mon Sep 17 00:00:00 2001 From: Nishanth Menon Date: Wed, 16 Feb 2011 10:50:13 +0530 Subject: [PATCH 09/14] OMAP3+: SR: make enable path use volt_data pointer Passing the volt_data pointers across allows us to save us the effort of looking up the voltage data pointer from the voltage value at multiple layers, we need to look at the voltage data in DVFS layer for further processing, so modify the APIs to pass the voltage data pointer all the way through to lower layers to the SmartReflex AVS class drivers. Change-Id: I26a74d43d801c6d0e236a76f81b483915426d808 Signed-off-by: Nishanth Menon --- arch/arm/mach-omap2/dvfs.c | 2 +- arch/arm/mach-omap2/pm44xx.c | 9 +++++--- arch/arm/mach-omap2/smartreflex-class3.c | 15 +++---------- arch/arm/mach-omap2/smartreflex.c | 27 ++++++++++++------------ arch/arm/mach-omap2/smartreflex.h | 8 ++++--- 5 files changed, 29 insertions(+), 32 deletions(-) diff --git a/arch/arm/mach-omap2/dvfs.c b/arch/arm/mach-omap2/dvfs.c index 4659e928987..38ae4576cdf 100644 --- a/arch/arm/mach-omap2/dvfs.c +++ b/arch/arm/mach-omap2/dvfs.c @@ -844,7 +844,7 @@ static int _dvfs_scale(struct device *req_dev, struct device *target_dev, __func__, voltdm->name); out: /* Re-enable Smartreflex module */ - omap_sr_enable(voltdm); + omap_sr_enable(voltdm, new_vdata); return ret; } diff --git a/arch/arm/mach-omap2/pm44xx.c b/arch/arm/mach-omap2/pm44xx.c index ed50649f7be..6685f9fb1e5 100644 --- a/arch/arm/mach-omap2/pm44xx.c +++ b/arch/arm/mach-omap2/pm44xx.c @@ -211,8 +211,10 @@ void omap4_enter_sleep(unsigned int cpu, unsigned int power_state, bool suspend) } omap_temp_sensor_resume_idle(); - omap_sr_enable(iva_voltdm); - omap_sr_enable(core_voltdm); + omap_sr_enable(iva_voltdm, + omap_voltage_get_curr_vdata(iva_voltdm)); + omap_sr_enable(core_voltdm, + omap_voltage_get_curr_vdata(core_voltdm)); } if (omap4_device_prev_state_off()) { @@ -232,7 +234,8 @@ void omap4_enter_sleep(unsigned int cpu, unsigned int power_state, bool suspend) if (mpu_next_state < PWRDM_POWER_INACTIVE) { omap_vc_set_auto_trans(mpu_voltdm, OMAP_VC_CHANNEL_AUTO_TRANSITION_DISABLE); - omap_sr_enable(mpu_voltdm); + omap_sr_enable(mpu_voltdm, + omap_voltage_get_curr_vdata(mpu_voltdm)); } diff --git a/arch/arm/mach-omap2/smartreflex-class3.c b/arch/arm/mach-omap2/smartreflex-class3.c index 44be57a9ede..faeca929609 100644 --- a/arch/arm/mach-omap2/smartreflex-class3.c +++ b/arch/arm/mach-omap2/smartreflex-class3.c @@ -13,20 +13,11 @@ #include "smartreflex.h" -static int sr_class3_enable(struct voltagedomain *voltdm) +static int sr_class3_enable(struct voltagedomain *voltdm, + struct omap_volt_data *volt_data) { - struct omap_volt_data *v = omap_voltage_get_curr_vdata(voltdm); - unsigned long volt; - - if (IS_ERR_OR_NULL(v)) { - pr_warning("%s: Curr voltage unknown. Cannot enable sr_%s\n", - __func__, voltdm->name); - return -ENODATA; - } - volt = omap_get_operation_voltage(v); - omap_vp_enable(voltdm); - return sr_enable(voltdm, volt); + return sr_enable(voltdm, volt_data); } static int sr_class3_disable(struct voltagedomain *voltdm, int is_volt_reset) diff --git a/arch/arm/mach-omap2/smartreflex.c b/arch/arm/mach-omap2/smartreflex.c index be05e953941..1f18c9587fa 100644 --- a/arch/arm/mach-omap2/smartreflex.c +++ b/arch/arm/mach-omap2/smartreflex.c @@ -304,6 +304,7 @@ static void sr_set_regfields(struct omap_sr *sr) static void sr_start_vddautocomp(struct omap_sr *sr) { + int r; if (!sr_class || !(sr_class->enable) || !(sr_class->configure)) { dev_warn(&sr->pdev->dev, "%s: smartreflex class driver not registered\n", @@ -322,7 +323,9 @@ static void sr_start_vddautocomp(struct omap_sr *sr) return; } - if (!sr_class->enable(sr->voltdm)) + r = sr_class->enable(sr->voltdm, + omap_voltage_get_curr_vdata(sr->voltdm)); + if (!r) sr->autocomp_active = true; mutex_unlock(&omap_dvfs_lock); } @@ -697,7 +700,7 @@ int sr_configure_minmax(struct voltagedomain *voltdm) /** * sr_enable() - Enables the smartreflex module. * @voltdm: VDD pointer to which the SR module to be configured belongs to. - * @volt: The voltage at which the Voltage domain associated with + * @volt_data: The voltage at which the Voltage domain associated with * the smartreflex module is operating at. * This is required only to program the correct Ntarget value. * @@ -705,10 +708,9 @@ int sr_configure_minmax(struct voltagedomain *voltdm) * enable a smartreflex module. Returns 0 on success. Returns error * value if the voltage passed is wrong or if ntarget value is wrong. */ -int sr_enable(struct voltagedomain *voltdm, unsigned long volt) +int sr_enable(struct voltagedomain *voltdm, struct omap_volt_data *volt_data) { u32 nvalue_reciprocal; - struct omap_volt_data *volt_data; struct omap_sr *sr = _sr_lookup(voltdm); int ret; @@ -718,19 +720,16 @@ int sr_enable(struct voltagedomain *voltdm, unsigned long volt) return -EINVAL; } - volt_data = omap_voltage_get_voltdata(sr->voltdm, volt); - - if (IS_ERR(volt_data)) { - dev_warn(&sr->pdev->dev, "%s: Unable to get voltage table" - "for nominal voltage %ld\n", __func__, volt); - return -ENODATA; + if (IS_ERR_OR_NULL(volt_data)) { + dev_warn(&sr->pdev->dev, "%s: bad voltage data\n", __func__); + return -EINVAL; } nvalue_reciprocal = sr_retrieve_nvalue(sr, volt_data->sr_efuse_offs); if (!nvalue_reciprocal) { dev_warn(&sr->pdev->dev, "%s: NVALUE = 0 at voltage %ld\n", - __func__, volt); + __func__, omap_get_operation_voltage(volt_data)); return -ENODATA; } @@ -894,13 +893,15 @@ int sr_register_class(struct omap_sr_class_data *class_data) * omap_sr_enable() - API to enable SR clocks and to call into the * registered smartreflex class enable API. * @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 enable * a particular smartreflex module. This API will do the initial * configurations to turn on the smartreflex module and in turn call * into the registered smartreflex class enable API. */ -void omap_sr_enable(struct voltagedomain *voltdm) +void omap_sr_enable(struct voltagedomain *voltdm, + struct omap_volt_data *volt_data) { struct omap_sr *sr = _sr_lookup(voltdm); @@ -919,7 +920,7 @@ void omap_sr_enable(struct voltagedomain *voltdm) return; } - sr_class->enable(voltdm); + sr_class->enable(voltdm, omap_voltage_get_curr_vdata(voltdm)); } /** diff --git a/arch/arm/mach-omap2/smartreflex.h b/arch/arm/mach-omap2/smartreflex.h index 8660049ba2d..22576e76c39 100644 --- a/arch/arm/mach-omap2/smartreflex.h +++ b/arch/arm/mach-omap2/smartreflex.h @@ -195,7 +195,8 @@ struct omap_smartreflex_dev_attr { * @class_priv_data: Class specific private data (optional) */ struct omap_sr_class_data { - int (*enable)(struct voltagedomain *voltdm); + int (*enable)(struct voltagedomain *voltdm, + struct omap_volt_data *volt_data); int (*disable)(struct voltagedomain *voltdm, int is_volt_reset); int (*init)(struct voltagedomain *voltdm, void *class_priv_data); int (*deinit)(struct voltagedomain *voltdm, void *class_priv_data); @@ -241,7 +242,8 @@ struct omap_sr_data { }; /* Smartreflex module enable/disable interface */ -void omap_sr_enable(struct voltagedomain *voltdm); +void omap_sr_enable(struct voltagedomain *voltdm, + struct omap_volt_data *volt_data); void omap_sr_disable(struct voltagedomain *voltdm); void omap_sr_disable_reset_volt(struct voltagedomain *voltdm); @@ -249,7 +251,7 @@ void omap_sr_disable_reset_volt(struct voltagedomain *voltdm); void omap_sr_register_pmic(struct omap_sr_pmic_data *pmic_data); /* Smartreflex driver hooks to be called from Smartreflex class driver */ -int sr_enable(struct voltagedomain *voltdm, unsigned long volt); +int sr_enable(struct voltagedomain *voltdm, struct omap_volt_data *volt_data); void sr_disable(struct voltagedomain *voltdm); int sr_notifier_control(struct voltagedomain *voltdm, bool enable); int sr_configure_errgen(struct voltagedomain *voltdm); From 98fd554bac6484e0991d5eb19970fc372045f533 Mon Sep 17 00:00:00 2001 From: Nishanth Menon Date: Tue, 29 Nov 2011 15:54:54 -0600 Subject: [PATCH 10/14] OMAP3+: SR: introduce class private data per voltage domain Introduce private data for class drivers to operate on per voltage domain. This removes the necessity for drivers such as SmartReflex AVS Class 1.5 drivers from maintaining a special lookup table which does not scale when number of voltage domains change depending on silicon. It also makes the life of such class drivers to be faster without having to do a double lookup to get to their voltage domain specific private data Change-Id: Icfcd7b229e56cfc1179d2aad1dbf09bb503e292a Signed-off-by: Nishanth Menon --- arch/arm/mach-omap2/smartreflex-class3.c | 8 ++++++-- arch/arm/mach-omap2/smartreflex.c | 24 ++++++++++++++---------- arch/arm/mach-omap2/smartreflex.h | 16 ++++++++++------ 3 files changed, 30 insertions(+), 18 deletions(-) diff --git a/arch/arm/mach-omap2/smartreflex-class3.c b/arch/arm/mach-omap2/smartreflex-class3.c index faeca929609..db4d7dd0e59 100644 --- a/arch/arm/mach-omap2/smartreflex-class3.c +++ b/arch/arm/mach-omap2/smartreflex-class3.c @@ -14,13 +14,16 @@ #include "smartreflex.h" static int sr_class3_enable(struct voltagedomain *voltdm, + void *voltdm_cdata, struct omap_volt_data *volt_data) { omap_vp_enable(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, + void *voltdm_cdata, + int is_volt_reset) { sr_disable_errgen(voltdm); omap_vp_disable(voltdm); @@ -31,7 +34,8 @@ static int sr_class3_disable(struct voltagedomain *voltdm, int is_volt_reset) return 0; } -static int sr_class3_configure(struct voltagedomain *voltdm) +static int sr_class3_configure(struct voltagedomain *voltdm, + void *voltdm_cdata) { return sr_configure_errgen(voltdm); } diff --git a/arch/arm/mach-omap2/smartreflex.c b/arch/arm/mach-omap2/smartreflex.c index 1f18c9587fa..a78c2274f93 100644 --- a/arch/arm/mach-omap2/smartreflex.c +++ b/arch/arm/mach-omap2/smartreflex.c @@ -56,6 +56,8 @@ struct omap_sr { struct list_head node; struct omap_sr_nvalue_table *nvalue_table; struct voltagedomain *voltdm; + /* Managed by class driver as needed */ + void *voltdm_cdata; struct dentry *dbg_dir; }; @@ -225,8 +227,8 @@ static irqreturn_t sr_interrupt(int irq, void *data) sr_info->irq_enabled = false; } else { /* If the caller reports inability to handle, disable as well */ - if (sr_class->notify && - sr_class->notify(sr_info->voltdm, value)) { + if (sr_class->notify && sr_class->notify(sr_info->voltdm, + sr_info->voltdm_cdata, value)) { dev_err(&sr_info->pdev->dev, "%s: Callback cant handle!" "status=0x%08x. Disabling to prevent spam!!\n", __func__, status); @@ -316,14 +318,15 @@ static void sr_start_vddautocomp(struct omap_sr *sr) mutex_lock(&omap_dvfs_lock); if (sr_class->init && - sr_class->init(sr->voltdm, sr_class->class_priv_data)) { + sr_class->init(sr->voltdm, &sr->voltdm_cdata, + sr_class->class_priv_data)) { dev_err(&sr->pdev->dev, "%s: SRClass initialization failed\n", __func__); mutex_unlock(&omap_dvfs_lock); return; } - r = sr_class->enable(sr->voltdm, + r = sr_class->enable(sr->voltdm, sr->voltdm_cdata, omap_voltage_get_curr_vdata(sr->voltdm)); if (!r) sr->autocomp_active = true; @@ -342,9 +345,9 @@ static void sr_stop_vddautocomp(struct omap_sr *sr) if (sr->autocomp_active) { /* Pause dvfs from interfereing with our operations */ mutex_lock(&omap_dvfs_lock); - sr_class->disable(sr->voltdm, 1); + sr_class->disable(sr->voltdm, sr->voltdm_cdata, 1); if (sr_class->deinit && - sr_class->deinit(sr->voltdm, + sr_class->deinit(sr->voltdm, &sr->voltdm_cdata, sr_class->class_priv_data)) { dev_err(&sr->pdev->dev, "%s: SR[%d]Class deinitialization failed\n", @@ -743,7 +746,7 @@ int sr_enable(struct voltagedomain *voltdm, struct omap_volt_data *volt_data) return 0; /* Configure SR */ - ret = sr_class->configure(voltdm); + ret = sr_class->configure(voltdm, sr->voltdm_cdata); if (ret) return ret; @@ -920,7 +923,8 @@ void omap_sr_enable(struct voltagedomain *voltdm, return; } - sr_class->enable(voltdm, omap_voltage_get_curr_vdata(voltdm)); + sr_class->enable(voltdm, sr->voltdm_cdata, + omap_voltage_get_curr_vdata(voltdm)); } /** @@ -953,7 +957,7 @@ void omap_sr_disable(struct voltagedomain *voltdm) return; } - sr_class->disable(voltdm, 0); + sr_class->disable(voltdm, sr->voltdm_cdata, 0); } /** @@ -986,7 +990,7 @@ void omap_sr_disable_reset_volt(struct voltagedomain *voltdm) return; } - sr_class->disable(voltdm, 1); + sr_class->disable(voltdm, sr->voltdm_cdata, 1); } /** diff --git a/arch/arm/mach-omap2/smartreflex.h b/arch/arm/mach-omap2/smartreflex.h index 22576e76c39..dcf6599afe8 100644 --- a/arch/arm/mach-omap2/smartreflex.h +++ b/arch/arm/mach-omap2/smartreflex.h @@ -195,13 +195,17 @@ struct omap_smartreflex_dev_attr { * @class_priv_data: Class specific private data (optional) */ struct omap_sr_class_data { - int (*enable)(struct voltagedomain *voltdm, + int (*enable)(struct voltagedomain *voltdm, void *voltdm_cdata, struct omap_volt_data *volt_data); - int (*disable)(struct voltagedomain *voltdm, int is_volt_reset); - int (*init)(struct voltagedomain *voltdm, void *class_priv_data); - int (*deinit)(struct voltagedomain *voltdm, void *class_priv_data); - int (*configure)(struct voltagedomain *voltdm); - int (*notify)(struct voltagedomain *voltdm, u32 status); + int (*disable)(struct voltagedomain *voltdm, void *voltdm_cdata, + int is_volt_reset); + int (*init)(struct voltagedomain *voltdm, void **voltdm_cdata, + void *class_priv_data); + int (*deinit)(struct voltagedomain *voltdm, void **voltdm_cdata, + void *class_priv_data); + int (*configure)(struct voltagedomain *voltdm, void *voltdm_cdata); + int (*notify)(struct voltagedomain *voltdm, void *voltdm_cdata, + u32 status); u8 notify_flags; u8 class_type; void *class_priv_data; From dd776f9319d4289f22f55615b1c6a6bfa55c6dc3 Mon Sep 17 00:00:00 2001 From: Nishanth Menon Date: Thu, 17 Feb 2011 21:26:34 +0530 Subject: [PATCH 11/14] OMAP3630+: SR: add support for class 1.5 Traditional SmartReflex AVS(Automatic Voltage Scaling) classes are: * Class 0 - Product test calibration Silicon is calibration at production floor and fused with voltages for each OPP * Class 1 - Boot time calibration Silicon is calibrated once at boot time and voltages are stored for the lifetime of operation. * Class 2 - Continuous s/w calibration SR module notifies s/w for any change in the system which is desired and the s/w makes runtime decisions in terms of setting the voltage, this mechanism could be used in the system which does not have PMIC capable of SR without using the voltage controller and voltage processor blocks. * Class 3 - Continuous h/w calibration SR module is switch on after reaching a voltage level and SR continuously monitors the system and makes runtime adjustments without s/w involvement. OMAP3430 has used SmartReflex AVS and with a a PMIC which understands the SR protocol, Class 3 has been used. With OMAP3630 onwards, a new SmartReflex AVS class of operation Class 1.5 was introduced. * Class 1.5 - Periodic s/w calibration This uses the h/w calibration loop and at the end of calibration stores the voltages to be used run time, periodic recalibration is performed as well. The operational mode is describes as the following: * SmartReflex AVS h/w calibration loop is essential to identify the optimal voltage for a given OPP. * Once this optimal voltage is detected, SmartReflex AVS loop is disabled in class 1.5 mode of operation. * Until there is a need for a recalibration, any further transition to an OPP voltage which is calibrated can use the calibrated voltage and does not require enabling the SR AVS h/w loop. * On a periodic basis (recommendation being once approximately every 24 hours), software is expected to perform a recalibration to find a new optimal voltage which is compensated for device aging. - For performing this recalibration, the start voltage does not need to be the nominal voltage anymore. instead, the system can start with a voltage which is 50mV higher than the previously calibrated voltage to identify the new optimal voltage as the aging factor within a period of 1 day is not going to be anywhere close to 50mV. - This "new starting point" for recalibration is called a dynamic nominal voltage for that voltage point. In short, with the introduction of SmartReflex class 1.5, there are three new voltages possible in a system's DVFS transition: * Nominal Voltage - The maximum voltage needed for a worst possible device in the worst possible conditions. This is the voltage we choose as the starting point for the h/w loop to optimize for the first time calibration on system bootup. * Dynamic Nominal Voltage - Worst case voltage for a specific device in considering the system aging on the worst process device. * Calibrated Voltage - Best voltage for the current device at a given point of time. In terms of the implementation, doing calibration involves waiting for the SmartReflex h/w loop to settle down, and doing this as part of the DVFS flow itself would increase the latency of DVFS transition when there is a need to calibrate that opp. instead, the calibration is performed "out of path" using a workqueue statemachine. The workqueue waits for the system stabilization, then enables VP interrupts to monitor for system instability interms of voltage oscillations that are reported back to the system as interrupts, in case of prolonged system oscillations, nominal voltage is chosen as a safe voltage and this event is logged in the system log for developer debug and fixing. For the recalibration, a common workqueue for all domains is started at the start of the class initialization and it resets the calibrated voltages on a periodic basis. For distros that may choose not to do the recommended periodic recalibration, instead choose to perform boot time calibration, kconfig configuration option is provided to do so. TODO: a) Cpuidle and suspend paths are not integrated with SmartReflex driver at this point. b) Since the SR registers are accessed and controlled in parallel to DVFS some sort of mechanism is necessary to be introduced along with OMAP DVFS layer to ensure mutual exclusivity c) Additional debug interfaces for vmin analysis for platform characterization and addition of system margin needs to be introduced from SmartReflex perspective. This implementation also includes the following contributors: Tony Lindgren for suggestion on using interrupt based mechanism instead of polling to detect voltage oscillations. Peter 'p2' De Schrijver for debating alternatives on recalibration mechanisms Paul Walmsey, Eduardo Valentin, Ambresh K, Igor Dmitriev and quiet a few others for patient review, testing and reporting of issues of a previous incarnation of this implemenation. Last, but not the least, the TI H/w team in introducing this new SR AVS class and patiently debating it's various facets. Change-Id: I6549c75bfb4401d47b657c5efc1e348dc3cd0c46 Signed-off-by: Nishanth Menon --- arch/arm/mach-omap2/Makefile | 1 + arch/arm/mach-omap2/smartreflex-class1p5.c | 699 +++++++++++++++++++++ arch/arm/mach-omap2/smartreflex-class3.c | 1 + arch/arm/mach-omap2/smartreflex.c | 31 +- arch/arm/mach-omap2/smartreflex.h | 14 +- arch/arm/mach-omap2/voltage.c | 86 ++- arch/arm/mach-omap2/voltage.h | 28 + arch/arm/plat-omap/Kconfig | 20 + 8 files changed, 875 insertions(+), 5 deletions(-) create mode 100644 arch/arm/mach-omap2/smartreflex-class1p5.c diff --git a/arch/arm/mach-omap2/Makefile b/arch/arm/mach-omap2/Makefile index 39b0c972a50..434d64cd34a 100644 --- a/arch/arm/mach-omap2/Makefile +++ b/arch/arm/mach-omap2/Makefile @@ -78,6 +78,7 @@ obj-$(CONFIG_ARCH_OMAP4) += prcm-debug.o endif 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$(plus_sec) diff --git a/arch/arm/mach-omap2/smartreflex-class1p5.c b/arch/arm/mach-omap2/smartreflex-class1p5.c new file mode 100644 index 00000000000..582054e2d37 --- /dev/null +++ b/arch/arm/mach-omap2/smartreflex-class1p5.c @@ -0,0 +1,699 @@ +/* + * 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 "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? + */ +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; +}; + +#if CONFIG_OMAP_SR_CLASS1P5_RECALIBRATION_DELAY +/* recal_work: recalibration calibration work */ +static struct delayed_work recal_work; +#endif + +static unsigned long class1p5_margin; + +/** + * 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; + 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); + 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]; + } + /* Use the nominal voltage as the safe voltage to recover bad osc */ + if (u_volt_safe > volt_data->volt_nominal) + u_volt_safe = volt_data->volt_nominal; + + + /* 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 (class1p5_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 + class1p5_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 = class1p5_margin; + } + /* Add margin IF we are lower than nominal */ + if ((u_volt_safe + u_volt_margin) < volt_data->volt_nominal) { + u_volt_safe += u_volt_margin; + } else { + pr_err("%s: %s could not add %ld[%ld] margin" + "to vnom %d curr_v=%ld\n", + __func__, voltdm->name, u_volt_margin, + class1p5_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\n", + __func__, voltdm->name, volt_data->volt_nominal, + volt_data->volt_calibrated); + /* + * 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; + 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; + /* 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); + } + + /* 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; + + 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->vdata, NULL, 0); + omap_voltage_calib_reset(voltdm); + voltdm_reset(voltdm); + + *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_margin_set() - add a margin on top of calibrated voltage + * @margin: add margin in uVolts + * + * Some platforms may need a margin, so provide an api which board files + * need to call and update internal data structure. + */ +void __init sr_class1p5_margin_set(unsigned int margin) +{ + class1p5_margin = margin; +} + +/** + * 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; + + /* Add 10mV margin as 4460 has Class3 ntarget values */ + if (!class1p5_margin && cpu_is_omap446x()) { + pr_info("%s: OMAP4460: add 10mV margin for class 1.5\n", + __func__); + class1p5_margin = 10000; + } + + 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); diff --git a/arch/arm/mach-omap2/smartreflex-class3.c b/arch/arm/mach-omap2/smartreflex-class3.c index db4d7dd0e59..24eea28f633 100644 --- a/arch/arm/mach-omap2/smartreflex-class3.c +++ b/arch/arm/mach-omap2/smartreflex-class3.c @@ -23,6 +23,7 @@ static int sr_class3_enable(struct voltagedomain *voltdm, static int sr_class3_disable(struct voltagedomain *voltdm, void *voltdm_cdata, + struct omap_volt_data *vdata, int is_volt_reset) { sr_disable_errgen(voltdm); diff --git a/arch/arm/mach-omap2/smartreflex.c b/arch/arm/mach-omap2/smartreflex.c index a78c2274f93..1a11b95f093 100644 --- a/arch/arm/mach-omap2/smartreflex.c +++ b/arch/arm/mach-omap2/smartreflex.c @@ -345,7 +345,8 @@ static void sr_stop_vddautocomp(struct omap_sr *sr) if (sr->autocomp_active) { /* Pause dvfs from interfereing with our operations */ mutex_lock(&omap_dvfs_lock); - sr_class->disable(sr->voltdm, sr->voltdm_cdata, 1); + sr_class->disable(sr->voltdm, sr->voltdm_cdata, + omap_voltage_get_curr_vdata(sr->voltdm), 1); if (sr_class->deinit && sr_class->deinit(sr->voltdm, &sr->voltdm_cdata, sr_class->class_priv_data)) { @@ -510,6 +511,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. @@ -957,7 +980,8 @@ void omap_sr_disable(struct voltagedomain *voltdm) return; } - sr_class->disable(voltdm, sr->voltdm_cdata, 0); + sr_class->disable(voltdm, sr->voltdm_cdata, + omap_voltage_get_curr_vdata(voltdm), 0); } /** @@ -990,7 +1014,8 @@ void omap_sr_disable_reset_volt(struct voltagedomain *voltdm) return; } - sr_class->disable(voltdm, sr->voltdm_cdata, 1); + sr_class->disable(voltdm, sr->voltdm_cdata, + omap_voltage_get_curr_vdata(voltdm), 1); } /** diff --git a/arch/arm/mach-omap2/smartreflex.h b/arch/arm/mach-omap2/smartreflex.h index dcf6599afe8..fffc62607a7 100644 --- a/arch/arm/mach-omap2/smartreflex.h +++ b/arch/arm/mach-omap2/smartreflex.h @@ -177,6 +177,7 @@ struct omap_smartreflex_dev_attr { #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 @@ -198,7 +199,7 @@ struct omap_sr_class_data { int (*enable)(struct voltagedomain *voltdm, void *voltdm_cdata, struct omap_volt_data *volt_data); int (*disable)(struct voltagedomain *voltdm, void *voltdm_cdata, - int is_volt_reset); + struct omap_volt_data *volt_data, int is_volt_reset); int (*init)(struct voltagedomain *voltdm, void **voltdm_cdata, void *class_priv_data); int (*deinit)(struct voltagedomain *voltdm, void **voltdm_cdata, @@ -264,6 +265,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) {} @@ -278,5 +280,15 @@ 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 + +#ifdef CONFIG_OMAP_SMARTREFLEX_CLASS1P5 +extern void sr_class1p5_margin_set(unsigned int margin); +#else +static inline void sr_class1p5_margin_set(unsigned int margin) { } #endif #endif diff --git a/arch/arm/mach-omap2/voltage.c b/arch/arm/mach-omap2/voltage.c index 60fcdaeea7c..da436e3e847 100644 --- a/arch/arm/mach-omap2/voltage.c +++ b/arch/arm/mach-omap2/voltage.c @@ -177,6 +177,8 @@ int voltdm_scale(struct voltagedomain *voltdm, * This API finds out the correct voltage the voltage domain is supposed * to be at and resets the voltage to that level. Should be used especially * while disabling any voltage compensation modules. + * + * NOTE: appropriate locks should be held for mutual exclusivity. */ void voltdm_reset(struct voltagedomain *voltdm) { @@ -259,7 +261,8 @@ struct omap_volt_data *omap_voltage_get_voltdata(struct voltagedomain *voltdm, } for (i = 0; vdd->volt_data[i].volt_nominal != 0; i++) { - if (vdd->volt_data[i].volt_nominal == volt) + if (vdd->volt_data[i].volt_nominal == volt || + omap_get_operation_voltage(&vdd->volt_data[i]) == volt) return &vdd->volt_data[i]; } @@ -337,6 +340,52 @@ static int vp_volt_debug_get(void *data, u64 *val) } DEFINE_SIMPLE_ATTRIBUTE(vp_volt_debug_fops, vp_volt_debug_get, NULL, "%llu\n"); +static int dyn_volt_debug_get(void *data, u64 *val) +{ + struct voltagedomain *voltdm = (struct voltagedomain *)data; + struct omap_volt_data *volt_data; + + if (!voltdm) { + pr_warning("%s: Wrong paramater passed\n", __func__); + return -EINVAL; + } + + volt_data = omap_voltage_get_curr_vdata(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; +} +DEFINE_SIMPLE_ATTRIBUTE(dyn_volt_debug_fops, dyn_volt_debug_get, NULL, + "%llu\n"); + +static int calib_volt_debug_get(void *data, u64 *val) +{ + struct voltagedomain *voltdm = (struct voltagedomain *)data; + struct omap_volt_data *volt_data; + + if (!voltdm) { + pr_warning("%s: Wrong paramater passed\n", __func__); + return -EINVAL; + } + + volt_data = omap_voltage_get_curr_vdata(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(calib_volt_debug_fops, calib_volt_debug_get, NULL, + "%llu\n"); + static int nom_volt_debug_get(void *data, u64 *val) { struct voltagedomain *voltdm = (struct voltagedomain *) data; @@ -386,6 +435,12 @@ static void __init voltdm_debugfs_init(struct dentry *voltage_dir, (void) debugfs_create_file("curr_nominal_volt", S_IRUGO, voltdm->debug_dir, (void *) voltdm, &nom_volt_debug_fops); + (void) debugfs_create_file("curr_dyn_nominal_volt", S_IRUGO, + voltdm->debug_dir, (void *) voltdm, + &dyn_volt_debug_fops); + (void) debugfs_create_file("curr_calibrated_volt", S_IRUGO, + voltdm->debug_dir, (void *) voltdm, + &calib_volt_debug_fops); } /** @@ -449,6 +504,35 @@ static struct voltagedomain *_voltdm_lookup(const char *name) return 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. + * + * NOTE: Appropriate locks must be held by calling path to ensure mutual + * exclusivity + */ +int omap_voltage_calib_reset(struct voltagedomain *voltdm) +{ + struct omap_volt_data *volt_data; + + if (!voltdm) { + pr_warning("%s: voltdm NULL!\n", __func__); + return -EINVAL; + } + + volt_data = voltdm->vdd->volt_data; + + /* reset the calibrated voltages as 0 */ + while (volt_data->volt_nominal) { + volt_data->volt_calibrated = 0; + volt_data++; + } + return 0; +} + /** * voltdm_add_pwrdm - add a powerdomain to a voltagedomain * @voltdm: struct voltagedomain * to add the powerdomain to diff --git a/arch/arm/mach-omap2/voltage.h b/arch/arm/mach-omap2/voltage.h index ce49e064b19..642febd901b 100644 --- a/arch/arm/mach-omap2/voltage.h +++ b/arch/arm/mach-omap2/voltage.h @@ -58,6 +58,9 @@ struct omap_vfsm_instance { u8 voltsetupoff_reg; }; +/* Dynamic nominal voltage margin common for OMAP3630 and OMAP4 */ +#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 identifier. @@ -127,6 +130,10 @@ struct omap_voltage_notifier { /** * struct omap_volt_data - Omap voltage specific data. * @voltage_nominal: The possible voltage value in uV + * @voltage_calibrated: The Calibrated voltage value in uV + * @voltage_dynamic_nominal: The run time optimized nominal voltage for + * the device. Dynamic nominal is the nominal voltage + * specialized for that OPP on the device in uV. * @sr_efuse_offs: The offset of the efuse register(from system * control module base address) from where to read * the n-target value for the smartreflex module. @@ -142,6 +149,8 @@ struct omap_voltage_notifier { */ struct omap_volt_data { u32 volt_nominal; + u32 volt_calibrated; + u32 volt_dynamic_nominal; u32 sr_efuse_offs; u8 sr_errminlimit; u8 vp_errgain; @@ -324,7 +333,26 @@ static inline unsigned long omap_get_operation_voltage( { if (!vdata) return 0; + 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; } +int omap_voltage_calib_reset(struct voltagedomain *voltdm); + #endif diff --git a/arch/arm/plat-omap/Kconfig b/arch/arm/plat-omap/Kconfig index 65adda72e6c..ef3763c988c 100644 --- a/arch/arm/plat-omap/Kconfig +++ b/arch/arm/plat-omap/Kconfig @@ -92,6 +92,26 @@ 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 + 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 (operates in AVS Class 1 mode). + 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 From c70d0702503f68d34b613f63f1a3d6d6821a95c5 Mon Sep 17 00:00:00 2001 From: Nishanth Menon Date: Sat, 19 Feb 2011 16:40:46 +0530 Subject: [PATCH 12/14] OMAP3430: SR: class3: restrict CPU to run on Use SmartReflex AVS Class3 initialization only for OMAP343x family of processors. Change-Id: I660326bf265541ba4e0feb52853ff6cb9b293aed Signed-off-by: Nishanth Menon --- arch/arm/mach-omap2/smartreflex-class3.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/arch/arm/mach-omap2/smartreflex-class3.c b/arch/arm/mach-omap2/smartreflex-class3.c index 24eea28f633..9ac1c99f269 100644 --- a/arch/arm/mach-omap2/smartreflex-class3.c +++ b/arch/arm/mach-omap2/smartreflex-class3.c @@ -11,6 +11,7 @@ * published by the Free Software Foundation. */ +#include #include "smartreflex.h" static int sr_class3_enable(struct voltagedomain *voltdm, @@ -52,6 +53,10 @@ static struct omap_sr_class_data class3_data = { /* Smartreflex Class3 init API to be called from board file */ static int __init sr_class3_init(void) { + /* Enable this class only for OMAP343x */ + if (!cpu_is_omap343x()) + return -EINVAL; + pr_info("SmartReflex Class3 initialized\n"); return sr_register_class(&class3_data); } From ce2832b7cfcb694978bc5b194a7a140ee589358c Mon Sep 17 00:00:00 2001 From: Nishanth Menon Date: Mon, 28 Nov 2011 16:07:15 -0600 Subject: [PATCH 13/14] OMAP4: PM: handle SR disable and reset error If the smartreflex class disable and reset returns error, abort the idle/suspend condition accordingly. Change-Id: I1f912848d54706e63c020eba9b7867b12680760f Signed-off-by: Nishanth Menon --- arch/arm/mach-omap2/pm44xx.c | 9 ++++++--- arch/arm/mach-omap2/smartreflex.c | 12 +++++++----- arch/arm/mach-omap2/smartreflex.h | 6 +++--- 3 files changed, 16 insertions(+), 11 deletions(-) diff --git a/arch/arm/mach-omap2/pm44xx.c b/arch/arm/mach-omap2/pm44xx.c index 6685f9fb1e5..aec1116830b 100644 --- a/arch/arm/mach-omap2/pm44xx.c +++ b/arch/arm/mach-omap2/pm44xx.c @@ -142,7 +142,8 @@ void omap4_enter_sleep(unsigned int cpu, unsigned int power_state, bool suspend) goto abort_gpio; if (mpu_next_state < PWRDM_POWER_INACTIVE) { - omap_sr_disable_reset_volt(mpu_voltdm); + if (omap_sr_disable_reset_volt(mpu_voltdm)) + goto abort_device_off; omap_vc_set_auto_trans(mpu_voltdm, OMAP_VC_CHANNEL_AUTO_TRANSITION_RETENTION); } @@ -154,8 +155,10 @@ void omap4_enter_sleep(unsigned int cpu, unsigned int power_state, bool suspend) * enabling AUTO RET requires SR to disabled, its done here for * now. Needs a relook to see if this can be optimized. */ - omap_sr_disable_reset_volt(iva_voltdm); - omap_sr_disable_reset_volt(core_voltdm); + if (omap_sr_disable_reset_volt(iva_voltdm)) + goto abort_device_off; + if (omap_sr_disable_reset_volt(core_voltdm)) + goto abort_device_off; omap_vc_set_auto_trans(core_voltdm, OMAP_VC_CHANNEL_AUTO_TRANSITION_RETENTION); if (!is_pm44xx_erratum(IVA_AUTO_RET_iXXX)) { diff --git a/arch/arm/mach-omap2/smartreflex.c b/arch/arm/mach-omap2/smartreflex.c index 1a11b95f093..05b1e854a4a 100644 --- a/arch/arm/mach-omap2/smartreflex.c +++ b/arch/arm/mach-omap2/smartreflex.c @@ -994,27 +994,29 @@ void omap_sr_disable(struct voltagedomain *voltdm) * into the registered smartreflex class disable API. This API will tell * the smartreflex class disable to reset the VP voltage after * disabling smartreflex. + * + * Returns result of transition request. */ -void omap_sr_disable_reset_volt(struct voltagedomain *voltdm) +int omap_sr_disable_reset_volt(struct voltagedomain *voltdm) { struct omap_sr *sr = _sr_lookup(voltdm); if (IS_ERR(sr)) { pr_warning("%s: omap_sr struct for sr_%s not found\n", __func__, voltdm->name); - return; + return -ENODEV; } if (!sr->autocomp_active) - return; + return 0; if (!sr_class || !(sr_class->disable)) { dev_warn(&sr->pdev->dev, "%s: smartreflex class driver not" "registered\n", __func__); - return; + return -ENODEV; } - sr_class->disable(voltdm, sr->voltdm_cdata, + return sr_class->disable(voltdm, sr->voltdm_cdata, omap_voltage_get_curr_vdata(voltdm), 1); } diff --git a/arch/arm/mach-omap2/smartreflex.h b/arch/arm/mach-omap2/smartreflex.h index fffc62607a7..1fecc1b1ca9 100644 --- a/arch/arm/mach-omap2/smartreflex.h +++ b/arch/arm/mach-omap2/smartreflex.h @@ -250,7 +250,7 @@ struct omap_sr_data { void omap_sr_enable(struct voltagedomain *voltdm, struct omap_volt_data *volt_data); void omap_sr_disable(struct voltagedomain *voltdm); -void omap_sr_disable_reset_volt(struct voltagedomain *voltdm); +int omap_sr_disable_reset_volt(struct voltagedomain *voltdm); /* API to register the pmic specific data with the smartreflex driver. */ void omap_sr_register_pmic(struct omap_sr_pmic_data *pmic_data); @@ -276,8 +276,8 @@ static inline int sr_notifier_control(struct voltagedomain *voltdm, return -EINVAL; } -static inline void omap_sr_disable_reset_volt( - struct voltagedomain *voltdm) {} +static inline int omap_sr_disable_reset_volt( + struct voltagedomain *voltdm) { return 0; } static inline void omap_sr_register_pmic( struct omap_sr_pmic_data *pmic_data) {} static inline bool is_sr_enabled(struct voltagedomain *voltdm) From 4624087e9fb50310bfccd6e5bafcd9616849730e Mon Sep 17 00:00:00 2001 From: Nishanth Menon Date: Tue, 29 Nov 2011 11:09:37 -0600 Subject: [PATCH 14/14] OMAP4: temp_sensor: provide sensible and traceable warnings few of the pr_xxx messages dont have function names resulting in debug logs needing to be grepped through. Further, when re-enterant logic is provided with successful return conditions like that of clocks already enabled/disabled, error message makes no sense and only debug messages make sense. Change-Id: If25faf462e1f6389cbbcff9f1f1164c3c7ccdb0e Signed-off-by: Nishanth Menon --- drivers/misc/omap_temp_sensor.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/drivers/misc/omap_temp_sensor.c b/drivers/misc/omap_temp_sensor.c index 6fc4d5522b5..1c2788ca44b 100644 --- a/drivers/misc/omap_temp_sensor.c +++ b/drivers/misc/omap_temp_sensor.c @@ -197,8 +197,8 @@ static int omap_read_current_temp(struct omap_temp_sensor *temp_sensor) adc &= (OMAP4_BGAP_TEMP_SENSOR_DTEMP_MASK); if (!temp_sensor->is_efuse_valid) - pr_err_once("Invalid EFUSE, Non-trimmed BGAP, \ - Temp not accurate\n"); + pr_err_once("%s: Invalid EFUSE, Non-trimmed BGAP," + "Temp not accurate\n", __func__ ); if (adc < OMAP_ADC_START_VALUE || adc > OMAP_ADC_END_VALUE) { pr_err("%s:Invalid adc code reported by the sensor %d", @@ -304,7 +304,7 @@ static int omap_temp_sensor_enable(struct omap_temp_sensor *temp_sensor) spin_lock_irqsave(&temp_sensor->lock, flags); if (temp_sensor->clk_on) { - pr_err("clock already on\n"); + pr_debug("%s: clock already on\n", __func__); goto out; } @@ -344,7 +344,7 @@ static int omap_temp_sensor_disable(struct omap_temp_sensor *temp_sensor) spin_lock_irqsave(&temp_sensor->lock, flags); if (!temp_sensor->clk_on) { - pr_err("clock already off\n"); + pr_debug("%s: clock already off\n", __func__); goto out; } temp = omap_temp_sensor_readl(temp_sensor, @@ -391,7 +391,8 @@ static void throttle_delayed_work_fn(struct work_struct *work) curr = omap_read_current_temp(temp_sensor); if (curr >= BGAP_THRESHOLD_T_HOT || curr < 0) { - pr_warn("OMAP temp read %d exceeds the threshold\n", curr); + pr_warn("%s: OMAP temp read %d exceeds the threshold\n", + __func__, curr); omap_thermal_throttle(); schedule_delayed_work(&temp_sensor->throttle_work, msecs_to_jiffies(THROTTLE_DELAY_MS));