From patchwork Sat Oct 24 05:15:52 2009 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Nishanth Menon X-Patchwork-Id: 55701 X-Patchwork-Delegate: khilman@deeprootsystems.com Received: from vger.kernel.org (vger.kernel.org [209.132.176.167]) by demeter.kernel.org (8.14.2/8.14.2) with ESMTP id n9O5GNMl018734 for ; Sat, 24 Oct 2009 05:16:23 GMT Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1752513AbZJXFQQ (ORCPT ); Sat, 24 Oct 2009 01:16:16 -0400 Received: (majordomo@vger.kernel.org) by vger.kernel.org id S1752338AbZJXFQQ (ORCPT ); Sat, 24 Oct 2009 01:16:16 -0400 Received: from arroyo.ext.ti.com ([192.94.94.40]:46662 "EHLO arroyo.ext.ti.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1751890AbZJXFQN (ORCPT ); Sat, 24 Oct 2009 01:16:13 -0400 Received: from dlep35.itg.ti.com ([157.170.170.118]) by arroyo.ext.ti.com (8.13.7/8.13.7) with ESMTP id n9O5G69d028998 (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-SHA bits=256 verify=NO); Sat, 24 Oct 2009 00:16:06 -0500 Received: from legion.dal.design.ti.com (localhost [127.0.0.1]) by dlep35.itg.ti.com (8.13.7/8.13.7) with ESMTP id n9O5G3Lm025223; Sat, 24 Oct 2009 00:16:03 -0500 (CDT) Received: from senorita (senorita.am.dhcp.ti.com [128.247.75.1]) by legion.dal.design.ti.com (8.11.7p1+Sun/8.11.7) with ESMTP id n9O5G2Z19768; Sat, 24 Oct 2009 00:16:02 -0500 (CDT) Received: by senorita (Postfix, from userid 1000) id 77CA0C1A4; Sat, 24 Oct 2009 00:15:54 -0500 (CDT) From: Nishanth Menon To: "linux-omap , Mike Chan , Rajendra Nayak , Roger Quadros , Kalle Jokiniemi , Teerth Reddy , Kevin Hilman , Paul Walmsley , Hogander Jouni , Nishanth Menon Subject: [PATCH 2/2 v3] OMAP3: PM: SR: SmartReflex Refactor Rev4.0 Date: Sat, 24 Oct 2009 00:15:52 -0500 Message-Id: <1256361352-9968-3-git-send-email-nm@ti.com> X-Mailer: git-send-email 1.6.3.3 In-Reply-To: <1256361352-9968-2-git-send-email-nm@ti.com> References: <1256361352-9968-1-git-send-email-nm@ti.com> <1256361352-9968-2-git-send-email-nm@ti.com> MIME-Version: 1.0 Sender: linux-omap-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-omap@vger.kernel.org diff --git a/arch/arm/mach-omap2/pm-debug.c b/arch/arm/mach-omap2/pm-debug.c index 767ebbc..34ff068 100644 --- a/arch/arm/mach-omap2/pm-debug.c +++ b/arch/arm/mach-omap2/pm-debug.c @@ -35,6 +35,7 @@ #include "cm.h" #include "pm.h" #include "prm-regbits-34xx.h" +#include "smartreflex.h" int omap2_pm_debug; @@ -617,6 +618,8 @@ static int __init pm_dbg_init(void) &voltage_off_while_idle, &pm_dbg_option_fops); + (void)sr_debugfs_create_entries(d); + pm_dbg_init_done = 1; return 0; diff --git a/arch/arm/mach-omap2/resource34xx.c b/arch/arm/mach-omap2/resource34xx.c index 04be4d2..3789f88 100644 --- a/arch/arm/mach-omap2/resource34xx.c +++ b/arch/arm/mach-omap2/resource34xx.c @@ -294,17 +294,23 @@ static int program_opp(int res, struct omap_opp *opp, int target_level, else raise = 0; +#ifdef CONFIG_OMAP_SMARTREFLEX + sr_vp_disable_both(t_opp, c_opp); +#endif for (i = 0; i < 2; i++) { if (i == raise) ret = program_opp_freq(res, target_level, current_level); #ifdef CONFIG_OMAP_SMARTREFLEX else - sr_voltagescale_vcbypass(t_opp, c_opp, + sr_voltage_set(t_opp, c_opp, opp[target_level].vsel, opp[current_level].vsel); #endif } +#ifdef CONFIG_OMAP_SMARTREFLEX + sr_vp_enable_both(t_opp, c_opp); +#endif return ret; } diff --git a/arch/arm/mach-omap2/smartreflex.c b/arch/arm/mach-omap2/smartreflex.c new file mode 100644 index 0000000..d506896 --- /dev/null +++ b/arch/arm/mach-omap2/smartreflex.c @@ -0,0 +1,1604 @@ +/* + * linux/arch/arm/mach-omap3/smartreflex.c + * + * OMAP34XX SmartReflex Voltage Control + * + * Copyright (C) 2009 Texas Instruments, Inc. + * Nishanth Menon + * + * Copyright (C) 2008 Nokia Corporation + * Kalle Jokiniemi + * + * Copyright (C) 2007 Texas Instruments, Inc. + * Lesly A M + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "prm.h" +#include "smartreflex.h" +#include "prm-regbits-34xx.h" + +/* MCUDISACK is expected to happen within 1uSec. */ +#define COUNT_TIMEOUT_MCUDISACK 200 + +/* VPINIDLE is expected to happen within 100uSec. Typical is 2uSec */ +#define COUNT_TIMEOUT_VPINIDLE 200 + +/* Time taken for setting the device - worst case as FS I2C + * Depends on SMPSWAITIME MIN/MAX Typical is 200uSec + */ +#define COUNT_TIMEOUT_TRANSDONE_SET 400 + +/* Time to clear out multiple transdone events typical is 3uSec */ +#define COUNT_TIMEOUT_TRANSDONE_CLR 50 + +/* Time For VCBypass mode for TWL4030 derivative chip. */ +#define COUNT_TIMEOUT_TWL4030_VCBYPASS 500 + +/* How many retries to do for I2C errors seen on bus for Forceupdate? */ +#define COUNT_RETRY_SMPSNOACK 4 + +#define SR_REGADDR(offset) (sr->srbase_addr + (offset)) + +/* Which function to use for setting voltage */ +#ifdef CONFIG_OMAP_VC_BYPASS_UPDATE +#define SR_CHOSEN_VOLTAGE_UPDATE_MECH sr_vc_bypass +#else +#define SR_CHOSEN_VOLTAGE_UPDATE_MECH sr_vp_forceupdate +#endif + +#ifdef CONFIG_OMAP_PM_NONE +struct omap_opp *mpu_opps; +struct omap_opp *dsp_opps; +struct omap_opp *l3_opps; +#endif + +static ssize_t omap_sr_vdd_autocomp_show(struct kobject *kobj, + struct kobj_attribute *attr, + char *buf); +static ssize_t omap_sr_vdd_autocomp_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t n); +/* Structure for Voltage processor */ +struct omap_sr_vp { + /* Store the commonly used register offsets. + * this saves a if condition decision + */ + u16 prm_vpx_status_offset; + u16 prm_vpx_config_offset; + u16 prm_vpx_stepmin_offset; + u16 prm_vpx_stepmax_offset; + u16 prm_vpx_limito_offset; + u32 prm_vpx_vlimito_timeout; + u8 prm_vpx_vlimito_shift; + u16 prm_vpx_voltage_offset; + u16 prm_vc_cmd_val_offset; + /* Store the defaults + * allowing us to save OCP read + * operation + */ + u32 vpconfig_value; + u32 vpstepmin_value; + u32 vpstepmax_value; + u32 vplimito_value; + u32 vpenable_mask; + u32 irqmask_trans_done; + u32 irqmask_smps_noack; +}; + +/* Structure for Smart Reflex */ +struct omap_sr { + u8 srid; + u8 prcm_vdd; + char *vdd_name; + struct kobj_attribute autocom_attr; + struct omap_opp **omap_opp; + /* SR activity marker */ + u8 is_sr_reset; + u8 is_autocomp_active; + u32 req_opp_no; + u32 sr_config_value; + u32 sr_errconfig_value; + u32 sr_n_mod_mask; + u8 sr_n_mod_shift; + u32 sr_p_mod_mask; + u8 sr_p_mod_shift; + struct clk *fclk; + struct clk *iclk; + void __iomem *srbase_addr; + char *iclk_name; + char *fclk_name; + /* Voltage processor for the specific SR module */ + struct omap_sr_vp vp; + /* This will contain the register offset on + * boot, replaced with the actual value + * as part of init routine + */ + u8 num_opp; + u8 opp_boundary; + u32 opp_nvalue[]; +}; + +/* A superset of all SRs in the system ordered by SRID */ +struct omap_sr_list { + u8 num_sr; + struct omap_sr *sr_list[]; +}; + +/* Definitions for 3430 Silicon */ +/* Smart Reflex 1 structure */ +static __initdata struct omap_sr omap34xx_sr1 = { + /* *INDENT-OFF* */ + .srid = SR1, + .prcm_vdd = PRCM_VDD1, + .vdd_name = "vdd1_opp", + .omap_opp = &mpu_opps, + .autocom_attr = { + .attr = { + .name = __stringify(sr_vdd1_autocomp), + .mode = 0644, + }, + .show = omap_sr_vdd_autocomp_show, + .store = omap_sr_vdd_autocomp_store, + }, + .is_sr_reset = 1, + .is_autocomp_active = 0, + .srbase_addr = (void *)OMAP34XX_SR1_BASE, + .fclk_name = "sr1_fck", + .iclk_name = "sr_l4_ick", + .sr_errconfig_value = SR1_ERRWEIGHT | SR1_ERRMAXLIMIT | + ERRCONFIG_VPBOUNDINTEN | ERRCONFIG_VPBOUNDINTST | + SR_CLKACTIVITY_IOFF_FON, + .sr_n_mod_mask = OMAP343X_SR1_SENNENABLE_MASK, + .sr_n_mod_shift = OMAP343X_SR1_SENNENABLE_SHIFT, + .sr_p_mod_mask = OMAP343X_SR1_SENPENABLE_MASK, + .sr_p_mod_shift = OMAP343X_SR1_SENPENABLE_SHIFT, + .vp = { + .prm_vpx_status_offset = OMAP3_PRM_VP1_STATUS_OFFSET, + .prm_vpx_config_offset = OMAP3_PRM_VP1_CONFIG_OFFSET, + .prm_vpx_stepmin_offset = OMAP3_PRM_VP1_VSTEPMIN_OFFSET, + .prm_vpx_stepmax_offset = OMAP3_PRM_VP1_VSTEPMAX_OFFSET, + .prm_vpx_limito_offset = OMAP3_PRM_VP1_VLIMITTO_OFFSET, + .prm_vpx_vlimito_timeout = PRM_VP1_VLIMITTO_TIMEOUT_US, + .prm_vpx_vlimito_shift = PRM_VP1_VLIMITTO_TIMEOUT_SHIFT, + .prm_vpx_voltage_offset = OMAP3_PRM_VP1_VOLTAGE_OFFSET, + .prm_vc_cmd_val_offset = OMAP3_PRM_VC_CMD_VAL_0_OFFSET, + .vpconfig_value = PRM_VP1_CONFIG_ERROROFFSET | + PRM_VP1_CONFIG_TIMEOUTEN, + .vpstepmin_value = PRM_VP1_VSTEPMIN_SMPSWAITTIMEMIN | + PRM_VP1_VSTEPMIN_VSTEPMIN, + .vpstepmax_value = PRM_VP1_VSTEPMAX_SMPSWAITTIMEMAX | + PRM_VP1_VSTEPMAX_VSTEPMAX, + .vplimito_value = PRM_VP1_VLIMITTO_VDDMAX | + PRM_VP1_VLIMITTO_VDDMIN, + .vpenable_mask = PRM_VP1_CONFIG_VPENABLE, + .irqmask_trans_done = VP1_IRQMASK_TRANSDONE, + .irqmask_smps_noack = OMAP3430_VP1_NOSMPSACK_ST, + }, + .num_opp = 5, + .opp_boundary = 3, + .opp_nvalue = { + OMAP343X_CONTROL_FUSE_OPP1_VDD1, + OMAP343X_CONTROL_FUSE_OPP2_VDD1, + OMAP343X_CONTROL_FUSE_OPP3_VDD1, + OMAP343X_CONTROL_FUSE_OPP4_VDD1, + OMAP343X_CONTROL_FUSE_OPP5_VDD1, + }, + /* *INDENT-ON* */ +}; + +/* Smart Reflex 2 structure */ +static __initdata struct omap_sr omap34xx_sr2 = { + /* *INDENT-OFF* */ + .srid = SR2, + .prcm_vdd = PRCM_VDD2, + .vdd_name = "vdd2_opp", + .omap_opp = &l3_opps, + .autocom_attr = { + .attr = { + .name = __stringify(sr_vdd2_autocomp), + .mode = 0644, + }, + .show = omap_sr_vdd_autocomp_show, + .store = omap_sr_vdd_autocomp_store, + }, + .is_sr_reset = 1, + .is_autocomp_active = 0, + .srbase_addr = (void *)OMAP34XX_SR2_BASE, + .fclk_name = "sr2_fck", + .iclk_name = "sr_l4_ick", + .sr_errconfig_value = SR2_ERRWEIGHT | SR2_ERRMAXLIMIT | + ERRCONFIG_VPBOUNDINTEN | ERRCONFIG_VPBOUNDINTST | + SR_CLKACTIVITY_IOFF_FON, + .sr_n_mod_mask = OMAP343X_SR2_SENNENABLE_MASK, + .sr_n_mod_shift = OMAP343X_SR2_SENNENABLE_SHIFT, + .sr_p_mod_mask = OMAP343X_SR2_SENPENABLE_MASK, + .sr_p_mod_shift = OMAP343X_SR2_SENPENABLE_SHIFT, + .vp = { + .prm_vpx_status_offset = OMAP3_PRM_VP2_STATUS_OFFSET, + .prm_vpx_config_offset = OMAP3_PRM_VP2_CONFIG_OFFSET, + .prm_vpx_stepmin_offset = OMAP3_PRM_VP2_VSTEPMIN_OFFSET, + .prm_vpx_stepmax_offset = OMAP3_PRM_VP2_VSTEPMAX_OFFSET, + .prm_vpx_limito_offset = OMAP3_PRM_VP2_VLIMITTO_OFFSET, + .prm_vpx_vlimito_timeout = PRM_VP2_VLIMITTO_TIMEOUT_US, + .prm_vpx_vlimito_shift = PRM_VP2_VLIMITTO_TIMEOUT_SHIFT, + .prm_vpx_voltage_offset = OMAP3_PRM_VP1_VOLTAGE_OFFSET, + .prm_vc_cmd_val_offset = OMAP3_PRM_VC_CMD_VAL_1_OFFSET, + .vpconfig_value = PRM_VP2_CONFIG_ERROROFFSET | + PRM_VP2_CONFIG_TIMEOUTEN, + .vpstepmin_value = PRM_VP2_VSTEPMIN_SMPSWAITTIMEMIN | + PRM_VP2_VSTEPMIN_VSTEPMIN, + .vpstepmax_value = PRM_VP2_VSTEPMAX_SMPSWAITTIMEMAX | + PRM_VP2_VSTEPMAX_VSTEPMAX, + .vplimito_value = PRM_VP2_VLIMITTO_VDDMAX | + PRM_VP2_VLIMITTO_VDDMIN, + .vpenable_mask = PRM_VP2_CONFIG_VPENABLE, + .irqmask_trans_done = VP2_IRQMASK_TRANSDONE, + .irqmask_smps_noack = OMAP3430_VP2_NOSMPSACK_ST, + }, + .num_opp = 3, + .opp_boundary = 3, + .opp_nvalue = { + OMAP343X_CONTROL_FUSE_OPP1_VDD2, + OMAP343X_CONTROL_FUSE_OPP2_VDD2, + OMAP343X_CONTROL_FUSE_OPP3_VDD2, + }, + /* *INDENT-ON* */ +}; + +/* SR list for 3430 */ +static __initdata struct omap_sr_list omap34xx_srlist = { + .num_sr = 2, + .sr_list = {&omap34xx_sr1, &omap34xx_sr2} +}; + +/* The final SR list */ +static struct omap_sr_list *omap_srlist; + +/*********************** OPP Accessor functions ****************************/ + +/** + * @brief *get_sr - get SR pointer from an SRID + * + * @param srid - vddid + * + * @return struct pointer if found, else BUG()s + */ +static inline struct omap_sr *get_sr(u8 srid) +{ + BUG_ON(srid > omap_srlist->num_sr); + return omap_srlist->sr_list[srid - 1]; +} + +/** + * @brief *get_sr_from_vdd - get the SR structure indexed by + * VDD ID + * + * @param vddid - vddid + * + * @return struct pointer if found, else BUG()s + */ +static inline struct omap_sr *get_sr_from_vdd(u8 vddid) +{ + /* Currently, the SRID and VDDID are the same, misusing it */ + return get_sr(vddid); +} + +/** + * @brief *get_sr_from_vdd_name - get the SR structure from + * sysfs name + * + * @param name -sysfs entry name + * + * @return sr struct pointer if found else NULL + */ +static struct omap_sr *get_sr_from_vdd_name(char *name) +{ + int i; + struct omap_sr *sr; + for (i = 0; i < omap_srlist->num_sr; i++) { + sr = omap_srlist->sr_list[i]; + if (!strcmp(name, sr->autocom_attr.attr.name)) + return sr; + } + /* Nothin found, BUG!! */ + BUG(); + return NULL; +} + +/** + * @brief get_current_opp_number_from_sr - get the current active OPP number + * from SR pointer + * + * @param sr struct pointer + * + * @return current OPP ID + */ +static inline int get_current_opp_number_from_sr(struct omap_sr *sr) +{ + return (*sr->omap_opp)[resource_get_level(sr->vdd_name)].opp_id; +} + +/** + * @brief get_vsel_for_opp - get the VSEL value for the SR/OPP combination + * + * @param sr struct pointer + * @param opp opp number desired for + * + * @return vsel value, if bad opp number is given, this will BUG() + */ +static inline u8 get_vsel_for_opp(struct omap_sr *sr, int opp) +{ + /* Dont ask me to derefence nonexistant OPPs! + * TODO: add check for valid OPPs here + */ + BUG_ON(opp > sr->num_opp); + return (*sr->omap_opp)[opp].vsel; +} + +/****************** SMART REFLEX DEBUGFS ENTRIES *************************/ + +#ifdef __SR_DEBUG + +/** + * @brief sr_debugfs_set - set variable with value + * + * @param data - variable pointer + * @param val - value to set + * + * @return - 0 + */ +static int sr_debugfs_set(void *data, u64 val) +{ + u32 *option = data; + + *option = val; + + return 0; +} + +/** + * @brief sr_debugfs_get - setup the params + * + * @param data - variable to set + * @param val - value to return + * + * @return - 0 + */ +static int sr_debugfs_get(void *data, u64 *val) +{ + u32 *option = data; + + *val = *option; + + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(sr_debugfs_option_fops, sr_debugfs_get, sr_debugfs_set, + "%llu\n"); + +/** + * @brief sr_debugfs_vselget - return the vsel value + * + * @param data - pointer to vp + * @param val - value we read back + * + * @return + */ +static int sr_debugfs_vselget(void *data, u64 *val) +{ + struct omap_sr_vp *vp = (struct omap_sr_vp *)data; + + *val = prm_read_mod_reg(OMAP3430_GR_MOD, vp->prm_vpx_voltage_offset); + + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(sr_debugfs_option_vsel_fops, sr_debugfs_vselget, NULL, + "%llu\n"); + +/* Temporary store of pm_debug directory entry */ +static __initdata struct dentry *stored_pm_d; + +/** + * @brief sr_debugfs_create_entries - create the Smart Reflex entries + * called from pm-debug, this just stores it for SR to use in late_init + * + * @param d - parent directory tree + * + * @return 0 if all ok, else returns with error + */ +int sr_debugfs_create_entries(struct dentry *d) +{ + stored_pm_d = d; + return 0; +} + +/** + * @brief sr_debugfs_create_entries_late - create the Smart Reflex entries - + * called as part of init sequence of SR uses the dentry registered early + * + * @param d - parent directory tree + * + * @return 0 if all ok, else returns with error + */ +static __init int sr_debugfs_create_entries_late(void) +{ + struct dentry *sr_dir; + struct dentry *sr_sub_dir; + int count; + + sr_dir = debugfs_create_dir("smartreflex", stored_pm_d); + if (IS_ERR(sr_dir)) + return PTR_ERR(sr_dir); + for (count = 0; count < omap_srlist->num_sr; count++) { + int i; + struct omap_sr *sr = get_sr(count + 1); + char name[] = "0"; + char nval_name[] = "nvalue_opp0"; + name[0] += sr->srid; + sr_sub_dir = debugfs_create_dir(name, sr_dir); + if (IS_ERR(sr_sub_dir)) + continue; + for (i = 0; i < sr->num_opp; i++) { + nval_name[strlen(nval_name) - 1]++; + (void)debugfs_create_file(nval_name, S_IRUGO | S_IWUGO, + sr_sub_dir, + &(sr->opp_nvalue[i]), + &sr_debugfs_option_fops); + } + (void)debugfs_create_file("sr_errconfig_value", + S_IRUGO | S_IWUGO, sr_sub_dir, + &(sr->sr_errconfig_value), + &sr_debugfs_option_fops); + (void)debugfs_create_file("sr_config_value", S_IRUGO | S_IWUGO, + sr_sub_dir, &(sr->sr_config_value), + &sr_debugfs_option_fops); + (void)debugfs_create_file("vpconfig_value", S_IRUGO | S_IWUGO, + sr_sub_dir, &(sr->vp.vpconfig_value), + &sr_debugfs_option_fops); + (void)debugfs_create_file("vpstepmin_value", S_IRUGO | S_IWUGO, + sr_sub_dir, &(sr->vp.vpstepmin_value), + &sr_debugfs_option_fops); + (void)debugfs_create_file("vpstepmax_value", S_IRUGO | S_IWUGO, + sr_sub_dir, &(sr->vp.vpstepmax_value), + &sr_debugfs_option_fops); + (void)debugfs_create_file("vplimito_value", S_IRUGO | S_IWUGO, + sr_sub_dir, &(sr->vp.vplimito_value), + &sr_debugfs_option_fops); + (void)debugfs_create_file("vplimito_value", S_IRUGO | S_IWUGO, + sr_sub_dir, &(sr->vp.vplimito_value), + &sr_debugfs_option_fops); + (void)debugfs_create_file("vsel", S_IRUGO, sr_sub_dir, + &(sr->vp), &sr_debugfs_option_vsel_fops); + } + + return 0; +} + +#else + +static inline int sr_debugfs_create_entries_late(void) +{ + return 0; +} +#endif /* __SR_DEBUG */ + +/****************** PMIC WEAK FUNCTIONS FOR TWL4030 derivatives **********/ + +/** + * @brief pmic_srinit - Power management IC initialization + * for smart reflex. The current code is written for TWL4030 + * derivatives, replace in board file if PMIC requires + * a different sequence + * + * @return result of operation + */ +int __weak __init omap_pmic_srinit(void) +{ + int ret = -ENODEV; +#ifdef CONFIG_TWL4030_CORE + u8 reg; + /* Enable SR on T2 */ + ret = twl4030_i2c_read_u8(TWL4030_MODULE_PM_RECEIVER, ®, + R_DCDC_GLOBAL_CFG); + + reg |= DCDC_GLOBAL_CFG_ENABLE_SRFLX; + ret |= twl4030_i2c_write_u8(TWL4030_MODULE_PM_RECEIVER, reg, + R_DCDC_GLOBAL_CFG); +#endif /* End of CONFIG_TWL4030_CORE */ + return ret; +} + +/** + * @brief omap_pmic_voltage_ramp_delay - how much should this pmic ramp delay + * Various PMICs have different ramp up and down delays. choose to implement + * in required pmic file to override this function. + * On TWL4030 derivatives: + * T2 SMPS slew rate (min) 4mV/uS, step size 12.5mV, + * 2us added as buffer. + * + * @param srid - which SR is this for? + * @param target_vsel - targetted voltage selction + * @param current_vsel - current voltage selection + * + * @return delay in uSeconds + */ +u32 __weak omap_pmic_voltage_ramp_delay(u8 srid, u8 target_vsel, + u8 current_vsel) +{ + u32 t2_smps_steps = abs(target_vsel - current_vsel); + u32 t2_smps_delay = ((t2_smps_steps * 125) / 40) + 2; + return t2_smps_delay; +} + +#ifdef CONFIG_OMAP_VC_BYPASS_UPDATE +/** + * @brief omap_pmic_voltage_cmds - hook for pmic command sequence + * to be send out which are specific to pmic to set a specific voltage. + * this should inturn call vc_send_command with the required sequence + * The current implementation is for TWL4030 derivatives + * + * @param srid - which SR is this for? + * @param target_vsel - what voltage is desired to be set? + * + * @return specific value to set. + */ +int __weak omap_pmic_voltage_cmds(u8 srid, u8 target_vsel) +{ + u8 reg_addr = (srid == SR1) ? R_VDD1_SR_CONTROL : R_VDD2_SR_CONTROL; + u16 timeout = COUNT_TIMEOUT_TWL4030_VCBYPASS; + return vc_send_command(R_SRI2C_SLAVE_ADDR, reg_addr, target_vsel, + &timeout); +} +#endif /* ifdef CONFIG_OMAP_VC_BYPASS_UPDATE */ + +/*********************** Voltage Controller functions *************************/ + +/** + * @brief vc_send_command - The actual command transmission using + * Voltage controller on I2C4 + * + * @param slave_addr - what is the PMIC slave address + * @param reg_addr - what is the register address I should be using? + * @param data - what value do you want to write here? + * @param timeout_us timeout in uSeconds - returns actual time left + * + * @return 0 if all ok, else error value + */ +int vc_send_command(u8 slave_addr, u8 reg_addr, u8 data, u16 *timeout_us) +{ + u32 value; + u32 count; + + if (unlikely(!timeout_us)) + return -EINVAL; + + /* timeout = timeout_us/10 -> each udelay event + * 1 udelay event every 50 iteration, assuming + * each iteration is instaneous, + * count = (timeout_us/10) * 50 or timeout_us * 5 + */ + count = *timeout_us * 5; + + value = (data << OMAP3430_DATA_SHIFT) | + (reg_addr << OMAP3430_REGADDR_SHIFT) | + (slave_addr << OMAP3430_SLAVEADDR_SHIFT); + + prm_write_mod_reg(value, OMAP3430_GR_MOD, + OMAP3_PRM_VC_BYPASS_VAL_OFFSET); + + value = prm_set_mod_reg_bits(OMAP3430_VALID, OMAP3430_GR_MOD, + OMAP3_PRM_VC_BYPASS_VAL_OFFSET); + /* + * Do continous 50 checks then follow with a 10usec delay, + * then check again + */ + do { + value = prm_read_mod_reg(OMAP3430_GR_MOD, + OMAP3_PRM_VC_BYPASS_VAL_OFFSET) + & OMAP3430_VALID; + /* should i wait? */ + if (value && (count % 50)) { + udelay(10); + *timeout_us -= 10; + } + count--; + } while (value && count); + if (!count) { + pr_err("VC:Command Timedout! slave_addr=0x%02X," + "reg=0x%02X, value=0x%02X", slave_addr, reg_addr, data); + return -ETIMEDOUT; + } + return 0; +} +EXPORT_SYMBOL(vc_send_command); + +/** + * @brief sr_vc_bypass - setup voltage using VC Bypass technique + * + * @param target_opp - target opp to go to + * @param current_opp - current opp + * @param target_vsel - which voltage to go to? + * @param current_vsel - current voltage + * + * @return -success or failure + */ +#ifdef CONFIG_OMAP_VC_BYPASS_UPDATE +static int sr_vc_bypass(struct omap_sr *sr, + u32 target_opp_no, u8 target_vsel, u8 current_vsel) +{ + int ret = 0; + struct omap_sr_vp *vp = &sr->vp; + u32 vpconfig_value; + + vpconfig_value = vp->vpconfig_value; + vpconfig_value |= + ((target_opp_no < + sr->opp_boundary) ? SR_ERRGAIN_LOWOP : SR_ERRGAIN_HIGHOPP) << + OMAP3430_ERRORGAIN_SHIFT; + vpconfig_value |= target_vsel << OMAP3430_INITVOLTAGE_SHIFT; + prm_write_mod_reg(vpconfig_value, OMAP3430_GR_MOD, + vp->prm_vpx_config_offset); + + prm_rmw_mod_reg_bits(OMAP3430_VC_CMD_ON_MASK, + (target_vsel << OMAP3430_VC_CMD_ON_SHIFT), + OMAP3430_GR_MOD, vp->prm_vc_cmd_val_offset); + + /* + * Various PMIC might need a set of commands + * provide hooks for specific PMICs to implement + */ + ret = omap_pmic_voltage_cmds(sr->srid, target_vsel); + + /* delay based on pmic */ + if (!ret) + udelay(omap_pmic_voltage_ramp_delay(sr->srid, + target_vsel, current_vsel)); + + WARN_ON(ret); + + return ret; +} +#endif /* ifdef CONFIG_OMAP_VC_BYPASS_UPDATE */ + +/********************* SR Private functions ***************************/ + +/** + * @brief sr_write_reg - write to Smart Reflex Register + * + * @param sr - pointer to SR structure + * @param offset - SR reg offset to write to + * @param value - value to write with + */ +static inline void sr_write_reg(struct omap_sr *sr, unsigned offset, u32 value) +{ + __raw_writel(value, SR_REGADDR(offset)); +} + +/** + * @brief sr_modify_reg - Modify a register and write a new value to a field + * + * @param sr -pointer to SR structure + * @param offset -register offset + * @param mask -mask to clear out + * @param value -value to write there + */ +static inline void sr_modify_reg(struct omap_sr *sr, unsigned offset, u32 mask, + u32 value) +{ + u32 reg_val; + + reg_val = __raw_readl(SR_REGADDR(offset)); + reg_val &= ~mask; + reg_val |= value; + + __raw_writel(reg_val, SR_REGADDR(offset)); +} + +/** + * @brief sr_read_reg - read a SR register + * + * @param sr - pointer to SR structure + * @param offset - register offset + * + * @return value in that register + */ +static inline u32 sr_read_reg(struct omap_sr *sr, unsigned offset) +{ + return __raw_readl(SR_REGADDR(offset)); +} + +/** + * @brief sr_vplimito_value -return the timeout value based on sysclk + * and desired timeout uSec + * + * @param sys_clk_speed - internal sysclk freq in hz + * @param timeout_us - timeout in uSec + * + * @return timeout value to use in VPLIMITTO:TIMEOUT reg + */ +static inline u32 sr_vplimito_value(u32 sys_clk_speed, u16 timeout_us) +{ + u32 timeout_val; + /* prevent round off errors, we will divide by 10 later */ + timeout_val = (sys_clk_speed / 100000); + timeout_val *= timeout_us; + timeout_val /= 10; + return timeout_val; +} + +/** + * @brief sr_clk_enable - SR clock enable + * + * @param sr - Structure to SR structure + * + * @return - result + */ +static int sr_clk_enable(struct omap_sr *sr) +{ + if (clk_enable(sr->iclk) != 0) { + pr_crit("SR:Could not enable %s for [%d]\n", + sr->iclk->name, sr->srid); + return -EINVAL; + } + if (clk_enable(sr->fclk) != 0) { + pr_crit("SR:Could not enable %s for [%d]\n", + sr->fclk->name, sr->srid); + clk_disable(sr->iclk); + return -EINVAL; + } + sr_modify_reg(sr, ERRCONFIG, + SR_CLKACTIVITY_MASK | ERRCONFIG_INTERRUPT_STATUS_MASK, + SR_CLKACTIVITY_IOFF_FON); + sr->is_sr_reset = 0; + + return 0; +} + +/** + * @brief sr_clk_disable - SR func clock disable + * + * @param sr - pointer to SR structure + */ +static void sr_clk_disable(struct omap_sr *sr) +{ + /* set fclk, iclk- idle */ + sr_modify_reg(sr, ERRCONFIG, + SR_CLKACTIVITY_MASK | ERRCONFIG_INTERRUPT_STATUS_MASK, + SR_CLKACTIVITY_IOFF_FOFF); + + clk_disable(sr->fclk); + clk_disable(sr->iclk); + sr->is_sr_reset = 1; +} + +/****************** Voltage processor functions ***************************/ + +/** + * @brief sr_vp_clear_vptransdone - clear vptrans_done event + * + * @param sr - sr pointer + * + * @return 0 if cleared ok, else 1 if timedout! + */ +static int sr_vp_clear_vptransdone(struct omap_sr *sr) +{ + struct omap_sr_vp *vp = &sr->vp; + u32 irqstat; + u32 count = COUNT_TIMEOUT_TRANSDONE_CLR; + do { + prm_write_mod_reg(vp->irqmask_trans_done, OCP_MOD, + OMAP3_PRM_IRQSTATUS_MPU_OFFSET); + irqstat = prm_read_mod_reg(OCP_MOD, + OMAP3_PRM_IRQSTATUS_MPU_OFFSET) & + vp->irqmask_trans_done; + if (irqstat) + udelay(1); + count--; + } while (count && irqstat); + if (!count) { + pr_crit("SR:VPTransdone[%d]:Timedout\n", sr->srid); + return -ETIMEDOUT; + } + return 0; +} + +/** + * @brief sr_vp_forceupdate - do a forceupdate method + * to update the voltage level + * + * @param sr - pointer to sr structure + * @param target_opp_no - targetted opp number + * @param target_vsel - targetted voltage level + * @param current_vsel - current voltage level + * + * @return 0 if all worked out, else 1 + */ +#ifndef CONFIG_OMAP_VC_BYPASS_UPDATE +static int sr_vp_forceupdate(struct omap_sr *sr, u32 target_opp_no, + u8 target_vsel, u8 current_vsel) +{ + u32 count; + u32 irqstat; + u32 vpconfig_value; + u32 retry_counter = COUNT_RETRY_SMPSNOACK; + + struct omap_sr_vp *vp = &sr->vp; + +retry_forceupdate: + /* First clear any pending events in the system */ + if (sr_vp_clear_vptransdone(sr)) { + pr_crit("SR:forceupdate-transdone1[%d]:timedout\n", sr->srid); + return -ETIMEDOUT; + } + + vpconfig_value = vp->vpconfig_value; + vpconfig_value |= + ((target_opp_no < + sr->opp_boundary) ? SR_ERRGAIN_LOWOP : SR_ERRGAIN_HIGHOPP) << + OMAP3430_ERRORGAIN_SHIFT; + vpconfig_value |= target_vsel << OMAP3430_INITVOLTAGE_SHIFT; + + prm_write_mod_reg(vpconfig_value, OMAP3430_GR_MOD, + vp->prm_vpx_config_offset); + + /* Trigger initVDD value copy to voltage processor */ + prm_set_mod_reg_bits(OMAP3430_INITVDD, OMAP3430_GR_MOD, + vp->prm_vpx_config_offset); + + /* Force update of voltage */ + prm_set_mod_reg_bits(OMAP3430_FORCEUPDATE, OMAP3430_GR_MOD, + vp->prm_vpx_config_offset); + + /* Clear initVDD copy trigger bit */ + prm_clear_mod_reg_bits(OMAP3430_INITVDD, OMAP3430_GR_MOD, + vp->prm_vpx_config_offset); + /* Clear force bit */ + prm_clear_mod_reg_bits(OMAP3430_FORCEUPDATE, OMAP3430_GR_MOD, + vp->prm_vpx_config_offset); + + /* Now wait for the i2c transactions to complete */ + count = COUNT_TIMEOUT_TRANSDONE_SET; + irqstat = 0; + do { + irqstat = prm_read_mod_reg(OCP_MOD, + OMAP3_PRM_IRQSTATUS_MPU_OFFSET) & + vp->irqmask_trans_done; + if (!irqstat) + udelay(1); + count--; + } while (count && !irqstat); + if (!count) { + irqstat = prm_read_mod_reg(OCP_MOD, + OMAP3_PRM_IRQSTATUS_MPU_OFFSET); + if (irqstat & vp->irqmask_smps_noack) { + WARN(irqstat, "SMPS NO ACK DETECTED[0x%08X]!!" + "ATTEMPTING RECOVERY [%d left]\n", + irqstat, retry_counter); + prm_write_mod_reg(irqstat, OCP_MOD, + OMAP3_PRM_IRQSTATUS_MPU_OFFSET); + retry_counter--; + if (retry_counter) + goto retry_forceupdate; + } + pr_crit("SR:forceupdate-transdone[%d]:timedout-" + "irqstat=0x%08X\n", sr->srid, irqstat); + BUG(); + return -ETIMEDOUT; + } + + /* + * Now we wait for voltage to rise on PMIC + */ + udelay(omap_pmic_voltage_ramp_delay(sr->srid, target_vsel, + current_vsel)); + + /* clear that event */ + if (sr_vp_clear_vptransdone(sr)) { + pr_crit("SR:forceupdate-transdone2[%d]:timedout\n", sr->srid); + BUG(); + return -ETIMEDOUT; + } + return 0; +} +#endif /* ifndef CONFIG_OMAP_VC_BYPASS_UPDATE */ + +/** + * @brief sr_vp_enable - enable VP enable code + * + * @param sr + */ +static int sr_vp_enable(struct omap_sr *sr, u32 target_opp_no) +{ + u32 vpconfig_value; + struct omap_sr_vp *vp = &sr->vp; + + /* Disable VP */ + prm_clear_mod_reg_bits(vp->vpenable_mask, OMAP3430_GR_MOD, + vp->prm_vpx_config_offset); + /* Clear INITVDD */ + prm_clear_mod_reg_bits(OMAP3430_INITVDD, OMAP3430_GR_MOD, + vp->prm_vpx_config_offset); + + vpconfig_value = vp->vpconfig_value; + vpconfig_value |= get_vsel_for_opp(sr, target_opp_no) << + OMAP3430_INITVOLTAGE_SHIFT; + vpconfig_value |= + ((target_opp_no < + sr->opp_boundary) ? SR_ERRGAIN_LOWOP : SR_ERRGAIN_HIGHOPP) << + OMAP3430_ERRORGAIN_SHIFT; + + prm_write_mod_reg(vpconfig_value, OMAP3430_GR_MOD, + vp->prm_vpx_config_offset); + + prm_write_mod_reg(vp->vpstepmin_value, OMAP3430_GR_MOD, + vp->prm_vpx_stepmin_offset); + prm_write_mod_reg(vp->vpstepmax_value, OMAP3430_GR_MOD, + vp->prm_vpx_stepmax_offset); + prm_write_mod_reg(vp->vplimito_value, OMAP3430_GR_MOD, + vp->prm_vpx_limito_offset); + + /* write1 to latch */ + prm_set_mod_reg_bits(OMAP3430_INITVDD, OMAP3430_GR_MOD, + vp->prm_vpx_config_offset); + /* write2 clear */ + prm_clear_mod_reg_bits(OMAP3430_INITVDD, OMAP3430_GR_MOD, + vp->prm_vpx_config_offset); + /* Enable VP */ + prm_set_mod_reg_bits(vp->vpenable_mask, OMAP3430_GR_MOD, + vp->prm_vpx_config_offset); + return 0; +} + +/** + * @brief sr_vp_disable - disbale Voltage processor + * + * @param sr - sr structure + * + * @return 0 if all ok, else return -ETIMEDOUT + */ +static int sr_vp_disable(struct omap_sr *sr) +{ + int count; + u32 v; + struct omap_sr_vp *vp = &sr->vp; + + v = prm_read_mod_reg(OMAP3430_GR_MOD, + vp->prm_vpx_config_offset) & vp->vpenable_mask; + /* Am i already disabled? */ + if (!v) { + pr_info("SR[%d] attempt to disable VP when already disabled!\n", + sr->srid); + return 0; + } + + /* Disable VP */ + prm_clear_mod_reg_bits(vp->vpenable_mask, OMAP3430_GR_MOD, + vp->prm_vpx_config_offset); + + /* Wait for vp to get idle - clear any events pending */ + count = COUNT_TIMEOUT_MCUDISACK; + do { + v = prm_read_mod_reg(OMAP3430_GR_MOD, + vp->prm_vpx_status_offset) & + PRM_VP_STATUS_VPINIDLE; + if (!v) + udelay(1); + count--; + } while (count && !v); + if (unlikely(!count)) { + v = prm_read_mod_reg(OCP_MOD, OMAP3_PRM_IRQSTATUS_MPU_OFFSET); + pr_warning("SR[%d]:vpdisable-vpinidle[opp=%d]:timedout-" + "irqstat=0x%08X\n", sr->srid, sr->req_opp_no, v); + return -ETIMEDOUT; + } + return 0; +} + +/** + * @brief sr_vp_configure - configure the basic SR structure + * + * @param sr - pointer to SR structure + */ +static void sr_vp_configure(struct omap_sr *sr) +{ + u32 target_opp_no; + u32 target_vsel; + u32 prm_vpx_voltage; + + target_opp_no = get_current_opp_number_from_sr(sr); + target_vsel = get_vsel_for_opp(sr, target_opp_no); + prm_vpx_voltage = prm_read_mod_reg(OMAP3430_GR_MOD, + sr->vp.prm_vpx_voltage_offset); + sr_vp_enable(sr, target_opp_no); + if (SR_CHOSEN_VOLTAGE_UPDATE_MECH + (sr, target_opp_no, target_vsel, prm_vpx_voltage)) + pr_crit("SR[%d] CONFIGURE VP failed!!\n", sr->srid); +} + +/** + * @brief sr_vp_reset_voltage - reset the voltages back to DVFS values + * + * @param srid -SRID + * + * @return 0 if ok, else result + */ +static int sr_vp_reset_voltage(u8 srid) +{ + u32 target_opp_no; + u32 target_vsel; + u32 prm_vpx_voltage; + struct omap_sr *sr; + + sr = get_sr(srid); + target_opp_no = sr->req_opp_no; + target_vsel = get_vsel_for_opp(sr, target_opp_no); + prm_vpx_voltage = prm_read_mod_reg(OMAP3430_GR_MOD, + sr->vp.prm_vpx_voltage_offset); + return SR_CHOSEN_VOLTAGE_UPDATE_MECH(sr, target_opp_no, + target_vsel, prm_vpx_voltage); + +} + +/*********************** SR functions *************************/ + +/** + * @brief sr_enable - enable smart reflex + * + * @param sr - smartreflex structure we are interest + * @param target_opp_no - target opp we want to switch to + * + * @return 0 if all went right, else return err val + */ +static int sr_enable(struct omap_sr *sr, u32 target_opp_no) +{ + u32 value; + + if (!sr->is_sr_reset) { + pr_info("SR[%d]already enabled\n", sr->srid); + return -EINVAL; + } + + sr->req_opp_no = target_opp_no; + + value = sr->opp_nvalue[target_opp_no - 1]; + if (value == 0) { + pr_info("SR[%d]:OPP%d doesn't support SmartReflex\n", + sr->srid, target_opp_no); + return -EINVAL; + } + + sr_clk_enable(sr); + /* Start with setting SREnable as 0 */ + sr_modify_reg(sr, SRCONFIG, SRCONFIG_SRENABLE, 0); + + sr_write_reg(sr, SRCONFIG, sr->sr_config_value); + + sr_write_reg(sr, NVALUERECIPROCAL, value); + + value = sr->sr_errconfig_value | + ((target_opp_no < sr->opp_boundary) ? SR_ERRMINLIMIT_LOWOPP : + SR_ERRMINLIMIT_HIGHOPP); + + sr_write_reg(sr, ERRCONFIG, value); + + /* SRCONFIG - enable SR */ + sr_modify_reg(sr, SRCONFIG, SRCONFIG_SRENABLE, SRCONFIG_SRENABLE); + + return 0; +} + +/** + * @brief sr_disable - disable Smart Reflex + * + * @param sr - pointer to sr structure of interest + * + * @return 0 if all went right, else return INVAL/TIMEDOUT + */ +int sr_disable(struct omap_sr *sr) +{ + int count; + u32 stat; + u32 value; + + if (sr->is_sr_reset) { + pr_info("SR[%d]-disable:already Disabled\n", sr->srid); + return -EINVAL; + } + value = sr_read_reg(sr, SRCONFIG) & SRCONFIG_SRENABLE; + if (!value) { + pr_info("SR[%d] attempt to disable SR when already disabled!\n", + sr->srid); + return 0; + } + value = sr->opp_nvalue[sr->req_opp_no - 1]; + if (value == 0) { + pr_info("SR[%d]-disable:" + "OPP%d doesn't support SmartReflex\n", + sr->srid, sr->req_opp_no); + return -EINVAL; + } + /* Enable the MCUDISACKINST */ + sr_modify_reg(sr, ERRCONFIG, ERRCONFIG_MCUDISACKINTEN | + ERRCONFIG_INTERRUPT_STATUS_MASK, + ERRCONFIG_MCUDISACKINTEN); + + /* SRCONFIG - disable SR */ + sr_modify_reg(sr, SRCONFIG, SRCONFIG_SRENABLE, 0); + + /* Disable VPBOUND interrupt and clear any status + * before actually waiting for disack + * should be done after sr is disabled + */ + sr_modify_reg(sr, ERRCONFIG, ERRCONFIG_VPBOUNDINTEN | + ERRCONFIG_INTERRUPT_STATUS_MASK, ERRCONFIG_VPBOUNDINTST); + + /* Wait for MCUDISACKINTST to be set */ + count = COUNT_TIMEOUT_MCUDISACK; + do { + stat = sr_read_reg(sr, ERRCONFIG) & ERRCONFIG_MCUDISACKINTST; + if (!stat) + udelay(1); + count--; + } while (count && !stat); + /* Clear the event and disable MCUDISACKINST */ + sr_modify_reg(sr, ERRCONFIG, ERRCONFIG_MCUDISACKINTEN | + ERRCONFIG_INTERRUPT_STATUS_MASK, + ERRCONFIG_MCUDISACKINTST); + + if (!count) { + pr_crit("SR[%d]-disable:MCUDIS timedout\n", sr->srid); + return -ETIMEDOUT; + } + sr_clk_disable(sr); + return 0; +} + +/**************** Common enable/disable functionality *********************/ + +/** + * @brief srvp_disable - disable SR and VP + * These functions are based on h/w timeouts and should ideally not fail. + * + * @param sr -sr struct + * + * @return result + */ +static int srvp_disable(struct omap_sr *sr) +{ + int ret; + /* If we dont have an nvalue, dont bother.. */ + if (!sr->opp_nvalue[sr->req_opp_no - 1]) { + pr_warning("SR[%d]: OPP%d does not support SR to disable\n", + sr->srid, sr->req_opp_no); + return -EINVAL; + } + ret = sr_vp_disable(sr); + if (unlikely(ret)) { + pr_err("SR[%d]: failed to disable vp:%d\n", sr->srid, ret); + } else { + ret = sr_disable(sr); + if (unlikely(ret)) + pr_err("SR[%d]: failed to disable sr:%d\n", + sr->srid, ret); + } + /* + * Does not make much sense renabling SR as + * system is going to be in an invalid state + */ + WARN_ON(ret); + return ret; +} + +/** + * @brief srvp_enable - enable SR and VP + * These functions are based on h/w timeouts and should ideally not fail. + * + * @param sr -sr struct + * @param target_opp -opp to go to + * + * @return result + */ +static int srvp_enable(struct omap_sr *sr, u32 target_opp) +{ + int ret; + + /* If we dont have an nvalue, dont bother.. */ + if (!sr->opp_nvalue[target_opp - 1]) { + pr_warning("SR[%d]: OPP%d does not support SR to enable\n", + sr->srid, target_opp); + return -EINVAL; + } + ret = sr_vp_enable(sr, target_opp); + if (unlikely(ret)) { + pr_err("SR[%d]: failed to enable vp:%d\n", sr->srid, ret); + } else { + ret = sr_enable(sr, target_opp); + /* Attempt to recover */ + if (unlikely(ret)) { + pr_err("SR[%d]: failed to enable sr:%d\n", sr->srid, + ret); + /* nothing we can do if vp_disable fails */ + (void)sr_vp_disable(sr); + } + } + /* + * potentially system in an invalid state - warn.. + */ + WARN_ON(ret); + return ret; +} + +/*********************** DVFS Entry POINTS **********************************/ + +/** + * @brief sr_vp_enable_both - enable both vp and sr + * + * @param target_opp - targetted op + * @param current_opp - current opp + * + * @return 0 if ok, 1 if not ok + */ +int sr_vp_enable_both(u32 target_opp, u32 current_opp) +{ + struct omap_sr *sr; + u32 vdd, target_opp_no; + int ret = 0; + + vdd = get_vdd(target_opp); + target_opp_no = get_opp_no(target_opp); + sr = get_sr_from_vdd(vdd); + + if (sr->is_autocomp_active && sr->is_sr_reset) { + ret = srvp_enable(sr, target_opp_no); + if (ret) { + pr_err("SR[%d]:enableboth:" + "failed enable SR\n", sr->srid); + } + } + return ret; +} +EXPORT_SYMBOL(sr_vp_enable_both); + +/** + * @brief sr_vp_disable_both - disable both vp and sr + * + * @param target_opp - targetted opp + * @param current_opp - current opp + * + * @return 0 if ok, 1 if not ok + */ +int sr_vp_disable_both(u32 target_opp, u32 current_opp) +{ + struct omap_sr *sr; + u32 vdd; + int ret = 0; + + vdd = get_vdd(target_opp); + sr = get_sr_from_vdd(vdd); + + if (sr->is_autocomp_active && !sr->is_sr_reset) { + ret = srvp_disable(sr); + if (ret) { + pr_err("SR[%d]:disableboth:" + "failed disable SR\n", sr->srid); + } + } + + return ret; + +} +EXPORT_SYMBOL(sr_vp_disable_both); + +/** + * @brief sr_voltage_set - setup a voltage requested + * + * @param target_opp - targetted opp + * @param current_opp - current opp + * @param target_vsel - targeted voltage + * @param current_vsel - current voltage + * + * @return - result of op -0 if ok, else value + */ +int sr_voltage_set(u32 target_opp, u32 current_opp, + u8 target_vsel, u8 current_vsel) +{ + struct omap_sr *sr; + u8 vdd, target_opp_no; + int ret; + + vdd = get_vdd(target_opp); + target_opp_no = get_opp_no(target_opp); + sr = get_sr_from_vdd(vdd); + + ret = + SR_CHOSEN_VOLTAGE_UPDATE_MECH(sr, target_opp_no, target_vsel, + current_vsel); + + return ret; +} +EXPORT_SYMBOL(sr_voltage_set); + +/*********************** CPUIDLE ENTRY POINTS *********************************/ + +/** + * @brief disable_smartreflex - disable SmartReflex before WFI + * + * @param srid SRID + */ +void disable_smartreflex(u8 srid) +{ + struct omap_sr *sr = NULL; + int ret; + u32 current_opp_no; + + /* I want to be in irq_disabled context.. + * else I will die.. find the rootcause and fix it instead + */ + BUG_ON(!irqs_disabled()); + + sr = get_sr(srid); + + current_opp_no = get_current_opp_number_from_sr(sr); + + if (sr->is_autocomp_active && !sr->is_sr_reset) { + sr->req_opp_no = current_opp_no; + ret = srvp_disable(sr); + if (ret) + pr_err("SR[%d]:disable_smartreflex:" + "failed disable SR\n", sr->srid); + } + + /* Reset the voltage for current OPP to nominal if SR was enabled */ + if (sr->is_autocomp_active) + sr_vp_reset_voltage(srid); +} +EXPORT_SYMBOL(disable_smartreflex); + +/** + * @brief enable_smartreflex - enable smart reflex after WFI is hit + * + * @param srid -SR ID to hit + */ +void enable_smartreflex(u8 srid) +{ + struct omap_sr *sr; + int ret; + u32 current_opp_no; + + /* I want to be in irq_disabled context.. + * else I will die.. find the rootcause and fix it instead + */ + BUG_ON(!irqs_disabled()); + + sr = get_sr(srid); + current_opp_no = get_current_opp_number_from_sr(sr); + + if (sr->is_autocomp_active && sr->is_sr_reset) { + ret = srvp_enable(sr, current_opp_no); + if (ret) + pr_err("SR[%d]:enable_smartreflex:" + "failed enable SR\n", sr->srid); + } +} +EXPORT_SYMBOL(enable_smartreflex); + +/*********************** SYSFS ENTRY POINTS *********************************/ +/** + * @brief omap_sr_vdd_autocomp_show - Sysfs entry for showing SR status + * + */ +static ssize_t omap_sr_vdd_autocomp_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + struct omap_sr *sr; + sr = get_sr_from_vdd_name((char *)attr->attr.name); + return sprintf(buf, "%d\n", sr->is_autocomp_active); +} + +/** + * @brief omap_sr_vdd_autocomp_store - enable/disable SR + * + */ +static ssize_t omap_sr_vdd_autocomp_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t n) +{ + unsigned short value; + struct omap_sr *sr; + int ret = 0; + char *name; + + name = (char *)attr->attr.name; + + sr = get_sr_from_vdd_name(name); + + if (sscanf(buf, "%hu", &value) != 1 || (value > 1)) { + pr_err("%s: Invalid value[%d]. Use 0 or 1\n", name, value); + return -EINVAL; + } + if (sr->is_autocomp_active == value) { + pr_info("%s: Already set to %d \n", name, value); + return n; + } + /* + * Sanity check-might happen if SR and dvfs/idle paths collide + * but unlikely though.. + */ + if (unlikely(value ^ sr->is_sr_reset)) { + pr_warning("%s: Is already set %d Vs %d.\n", name, value, + sr->is_sr_reset); + return n; + } + + if (value) { + u32 current_vddopp_no = get_current_opp_number_from_sr(sr); + ret = srvp_enable(sr, current_vddopp_no); + } else { + ret = srvp_disable(sr); + /* reset the voltage back to nominal */ + sr_vp_configure(sr); + } + if (!ret) { + sr->is_autocomp_active = value; + ret = n; + } + + return ret; +} + +/*********************** INIT FUNCTIONS *************************************/ + +/** + * @brief srvp_init - configure smart reflex and VP params + * + * @param sr - structure of sr of interest + */ +static void __init srvp_init(struct omap_sr *sr) +{ + struct clk *sys_ck; + u32 sys_clk_speed; + u32 clk_len; + u32 senn_mod, senp_mod; + u8 opp_count; + + /* Grab the clock speed */ + sys_ck = clk_get(NULL, "sys_ck"); + sys_clk_speed = clk_get_rate(sys_ck); + clk_put(sys_ck); + + switch (sys_clk_speed) { + case 12000000: + clk_len = SRCLKLENGTH_12MHZ_SYSCLK; + break; + case 13000000: + clk_len = SRCLKLENGTH_13MHZ_SYSCLK; + break; + case 19200000: + clk_len = SRCLKLENGTH_19MHZ_SYSCLK; + break; + case 26000000: + clk_len = SRCLKLENGTH_26MHZ_SYSCLK; + break; + case 38400000: + clk_len = SRCLKLENGTH_38MHZ_SYSCLK; + break; + default: + pr_err("SR[%d]:Invalid sysclk value: %d\n", sr->srid, + sys_clk_speed); + BUG(); + return; + } + /* The original nvalue contents were the register offsets + * now we replace them with the nvalue to be used elsewhere + */ + for (opp_count = 0; opp_count < sr->num_opp; opp_count++) + sr->opp_nvalue[opp_count] = + omap_ctrl_readl(sr->opp_nvalue[opp_count]); + + sr->vp.vplimito_value |= sr_vplimito_value(sys_clk_speed, + sr->vp.prm_vpx_vlimito_timeout) + << sr->vp.prm_vpx_vlimito_shift; + senn_mod = (omap_ctrl_readl(OMAP343X_CONTROL_FUSE_SR) & + sr->sr_n_mod_mask) >> sr->sr_n_mod_shift; + senp_mod = (omap_ctrl_readl(OMAP343X_CONTROL_FUSE_SR) & + sr->sr_p_mod_mask) >> sr->sr_p_mod_shift; + sr->sr_config_value = + (clk_len << SRCONFIG_SRCLKLENGTH_SHIFT) | SRCONFIG_ERRGEN_EN | + SRCONFIG_SENENABLE | (senn_mod << SRCONFIG_SENNENABLE_SHIFT) | + (senp_mod << SRCONFIG_SENPENABLE_SHIFT) | SRCONFIG_DELAYCTRL; + + /* Set up nominal voltage */ + sr_vp_configure(sr); +} + +/** + * @brief omap_sr_init - SR initialization + * + * @return 0 + */ +static int __init omap_sr_init(void) +{ + int ret = 0; + int i; + ret = omap_pmic_srinit(); + if (ret) { + pr_crit("PMIC init failed during SmartReflex initilization." + "connectivity issues?: %d\n", ret); + return ret; + } + if (cpu_is_omap3430()) { + omap_srlist = kzalloc(sizeof(omap34xx_srlist), GFP_KERNEL); + if (!omap_srlist) { + ret = -ENOMEM; + goto fail_cleanup; + } + omap_srlist->num_sr = omap34xx_srlist.num_sr; + for (i = 0; i < omap_srlist->num_sr; i++) { + int size = sizeof(*omap34xx_srlist.sr_list[i]); + size += + sizeof(u32) * omap34xx_srlist.sr_list[i]->num_opp; + omap_srlist->sr_list[i] = kzalloc(size, GFP_KERNEL); + if (!omap_srlist->sr_list[i]) { + ret = -ENOMEM; + goto fail_cleanup; + } + memcpy(omap_srlist->sr_list[i], + omap34xx_srlist.sr_list[i], size); + } + } else { + pr_warning("SmartReflex is not supported on this OMAP\n"); + return -ENODEV; + } + + /* Create the userspace control knobs */ + for (i = 0; i < omap_srlist->num_sr; i++) { + struct omap_sr *sr = get_sr(i + 1); + sr->fclk = clk_get(NULL, sr->fclk_name); + sr->iclk = clk_get(NULL, sr->iclk_name); + sr->srbase_addr = ioremap((u32) sr->srbase_addr, SZ_4K); + srvp_init(sr); + if (sysfs_create_file(power_kobj, &sr->autocom_attr.attr)) + pr_warning("SR: sysfs_create_file failed[%d]: %d\n", i, + ret); + } + if (sr_debugfs_create_entries_late()) + pr_warning("SR: debugfs_create_file failed\n"); + + pr_info("SmartReflex driver initialized\n"); + return 0; +fail_cleanup: + pr_warning("Out of memory..SmartReflex not initialized\n"); + if (omap_srlist) { + /* kfree is nullsafe */ + for (i = 0; i < omap_srlist->num_sr; i++) + kfree(omap_srlist->sr_list[i]); + kfree(omap_srlist); + } + return ret; +} +late_initcall(omap_sr_init); diff --git a/arch/arm/mach-omap2/smartreflex.h b/arch/arm/mach-omap2/smartreflex.h new file mode 100644 index 0000000..421f8b6 --- /dev/null +++ b/arch/arm/mach-omap2/smartreflex.h @@ -0,0 +1,274 @@ +#ifndef __ARCH_ARM_MACH_OMAP3_SMARTREFLEX_H +#define __ARCH_ARM_MACH_OMAP3_SMARTREFLEX_H +/* + * linux/arch/arm/mach-omap2/smartreflex.h + * + * Copyright (C) 2009 Texas Instruments, Inc. + * Nishanth Menon + * + * Copyright (C) 2008 Nokia Corporation + * Kalle Jokiniemi + * + * Copyright (C) 2007 Texas Instruments, Inc. + * Lesly A M + * + * 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. + */ + +/* SMART REFLEX REG ADDRESS OFFSET */ +#define SRCONFIG 0x00 +#define SRSTATUS 0x04 +#define SENVAL 0x08 +#define SENMIN 0x0C +#define SENMAX 0x10 +#define SENAVG 0x14 +#define AVGWEIGHT 0x18 +#define NVALUERECIPROCAL 0x1C +#define SENERROR 0x20 +#define ERRCONFIG 0x24 + +/* SR Modules */ +#define SR1 1 +#define SR2 2 + +#define VP1_IRQMASK_TRANSDONE (0x1 << 15) +#define VP2_IRQMASK_TRANSDONE (0x1 << 21) + +/* PRM_VP1_CONFIG */ +#define PRM_VP1_CONFIG_ERROROFFSET (0x00 << 24) +#define PRM_VP1_CONFIG_ERRORGAIN (0x18 << 16) +#define SR_ERRGAIN_LOWOP (0x0C) +#define SR_ERRGAIN_HIGHOPP (0x18) +#define SR_ERRMINLIMIT_LOWOPP (0xF4 << 0) +#define SR_ERRMINLIMIT_HIGHOPP (0xF9 << 0) +#define PRM_VP_STATUS_VPINIDLE (0x1 << 0) + +#define PRM_VP1_CONFIG_INITVOLTAGE (0x30 << 8) /* 1.2 volt */ +#define PRM_VP1_CONFIG_TIMEOUTEN (0x1 << 3) +#define PRM_VP1_CONFIG_INITVDD (0x1 << 2) +#define PRM_VP1_CONFIG_FORCEUPDATE (0x1 << 1) +#define PRM_VP1_CONFIG_VPENABLE (0x1 << 0) + +/* PRM_VP1_VSTEPMIN */ +#define PRM_VP1_VSTEPMIN_SMPSWAITTIMEMIN (0x03C << 8) +#define PRM_VP1_VSTEPMIN_VSTEPMIN (0x01 << 0) + +/* PRM_VP1_VSTEPMAX */ +#define PRM_VP1_VSTEPMAX_SMPSWAITTIMEMAX (0x003C << 8) +#define PRM_VP1_VSTEPMAX_VSTEPMAX (0x08 << 0) + +/* PRM_VP1_VLIMITTO */ +#define PRM_VP1_VLIMITTO_VDDMAX (0x44 << 24) +#define PRM_VP1_VLIMITTO_VDDMIN (0x14 << 16) +#define PRM_VP1_VLIMITTO_TIMEOUT_US (200) +#define PRM_VP1_VLIMITTO_TIMEOUT_SHIFT (0) + +/* PRM_VP2_CONFIG */ +#define PRM_VP2_CONFIG_ERROROFFSET (0x00 << 24) +#define PRM_VP2_CONFIG_ERRORGAIN (0x18 << 16) + +#define PRM_VP2_CONFIG_INITVOLTAGE (0x30 << 8) /* 1.2 volt */ +#define PRM_VP2_CONFIG_TIMEOUTEN (0x1 << 3) +#define PRM_VP2_CONFIG_INITVDD (0x1 << 2) +#define PRM_VP2_CONFIG_FORCEUPDATE (0x1 << 1) +#define PRM_VP2_CONFIG_VPENABLE (0x1 << 0) + +/* PRM_VP2_VSTEPMIN */ +#define PRM_VP2_VSTEPMIN_SMPSWAITTIMEMIN (0x03C << 8) +#define PRM_VP2_VSTEPMIN_VSTEPMIN (0x01 << 0) + +/* PRM_VP2_VSTEPMAX */ +#define PRM_VP2_VSTEPMAX_SMPSWAITTIMEMAX (0x003C << 8) +#define PRM_VP2_VSTEPMAX_VSTEPMAX (0x08 << 0) + +/* PRM_VP2_VLIMITTO */ +#define PRM_VP2_VLIMITTO_VDDMAX (0x42 << 24) +#define PRM_VP2_VLIMITTO_VDDMIN (0x18 << 16) +#define PRM_VP2_VLIMITTO_TIMEOUT_US (200) +#define PRM_VP2_VLIMITTO_TIMEOUT_SHIFT (0) + +/* SRCONFIG */ +#define SRCLKLENGTH_12MHZ_SYSCLK 0x3C +#define SRCLKLENGTH_13MHZ_SYSCLK 0x41 +#define SRCLKLENGTH_19MHZ_SYSCLK 0x60 +#define SRCLKLENGTH_26MHZ_SYSCLK 0x82 +#define SRCLKLENGTH_38MHZ_SYSCLK 0xC0 + +#define SRCONFIG_SRCLKLENGTH_SHIFT 12 +#define SRCONFIG_SENNENABLE_SHIFT 5 +#define SRCONFIG_SENPENABLE_SHIFT 3 + +#define SRCONFIG_SRENABLE (0x01 << 11) +#define SRCONFIG_SENENABLE (0x01 << 10) +#define SRCONFIG_ERRGEN_EN (0x01 << 9) +#define SRCONFIG_MINMAXAVG_EN (0x01 << 8) + +#define SRCONFIG_DELAYCTRL (0x01 << 2) +#define SRCONFIG_CLKCTRL (0x00 << 0) + +/* NVALUERECIPROCAL */ +#define NVALUERECIPROCAL_SENPGAIN_SHIFT 20 +#define NVALUERECIPROCAL_SENNGAIN_SHIFT 16 +#define NVALUERECIPROCAL_RNSENP_SHIFT 8 +#define NVALUERECIPROCAL_RNSENN_SHIFT 0 + +/* ERRCONFIG */ +#define SR_CLKACTIVITY_MASK (0x03 << 20) +#define SR_ERRWEIGHT_MASK (0x07 << 16) +#define SR_ERRMAXLIMIT_MASK (0xFF << 8) +#define SR_ERRMINLIMIT_MASK (0xFF << 0) + +#define SR_CLKACTIVITY_IOFF_FOFF (0x00 << 20) +#define SR_CLKACTIVITY_IOFF_FON (0x02 << 20) + +#define ERRCONFIG_VPBOUNDINTEN (0x1 << 31) +#define ERRCONFIG_MCUDISACKINTEN (0x1 << 23) + +/* Status Bits */ +#define ERRCONFIG_VPBOUNDINTST (0x1 << 30) +#define ERRCONFIG_MCUACCUMINTST (0x1 << 28) +#define ERRCONFIG_MCUVALIDINTST (0x1 << 26) +#define ERRCONFIG_MCUBOUNDINTST (0x1 << 24) +#define ERRCONFIG_MCUDISACKINTST (0x1 << 22) + +/* WARNING: Ensure all access to errconfig register skips + * clearing intst bits to ensure that we dont clear status + * bits unwantedly.. esp vpbound + */ +#define ERRCONFIG_INTERRUPT_STATUS_MASK (ERRCONFIG_VPBOUNDINTST |\ + ERRCONFIG_MCUACCUMINTST |\ + ERRCONFIG_MCUVALIDINTST |\ + ERRCONFIG_MCUBOUNDINTST |\ + ERRCONFIG_MCUDISACKINTST | (0X1<<19)) + +#define SR1_ERRWEIGHT (0x04 << 16) +#define SR1_ERRMAXLIMIT (0x02 << 8) +#define SR1_ERRMINLIMIT (0xFA << 0) + +#define SR2_ERRWEIGHT (0x04 << 16) +#define SR2_ERRMAXLIMIT (0x02 << 8) +#define SR2_ERRMINLIMIT (0xFA << 0) + +/* T2 SMART REFLEX */ +#define R_SRI2C_SLAVE_ADDR 0x12 +#define R_VDD1_SR_CONTROL 0x00 +#define R_VDD2_SR_CONTROL 0x01 + +/* VDDs*/ +#define PRCM_VDD1 1 +#define PRCM_VDD2 2 + +/* + * XXX: These should be removed/moved from here once we have a working DVFS + * implementation in place + */ +#define PHY_TO_OFF_PM_MASTER(p) (p - 0x36) +#define PHY_TO_OFF_PM_RECIEVER(p) (p - 0x5b) +#define PHY_TO_OFF_PM_INT(p) (p - 0x2e) + +/* Vmode control */ +#define R_DCDC_GLOBAL_CFG PHY_TO_OFF_PM_RECIEVER(0x61) +/* R_DCDC_GLOBAL_CFG register, SMARTREFLEX_ENABLE values */ +#define DCDC_GLOBAL_CFG_ENABLE_SRFLX 0x08 + +/* DEVICE ID/DPLL ID/CLOCK ID: bits 28-31 for OMAP type */ +#define OMAP_TYPE_SHIFT 28 +#define OMAP_TYPE_MASK 0xF +/* OPP ID: bits: 0-4 for OPP number */ +#define OPP_NO_POS 0 +#define OPP_NO_MASK 0x1F +/* OPP ID: bits: 5-6 for VDD */ +#define VDD_NO_POS 5 +#define VDD_NO_MASK 0x3 +/* Other IDs: bits 20-27 for ID type */ +/* These IDs have bits 25,26,27 as 1 */ +#define OTHER_ID_TYPE_SHIFT 20 +#define OTHER_ID_TYPE_MASK 0xFF + +#define OTHER_ID_TYPE(X) ((X & OTHER_ID_TYPE_MASK) << OTHER_ID_TYPE_SHIFT) +#define ID_OPP_NO(X) ((X & OPP_NO_MASK) << OPP_NO_POS) +#define ID_VDD(X) ((X & VDD_NO_MASK) << VDD_NO_POS) +#define OMAP(X) ((X >> OMAP_TYPE_SHIFT) & OMAP_TYPE_MASK) +#define get_opp_no(X) ((X >> OPP_NO_POS) & OPP_NO_MASK) +#define get_vdd(X) ((X >> VDD_NO_POS) & VDD_NO_MASK) + +/* XXX: end remove/move */ + +/* XXX: find more appropriate place for these once DVFS is in place */ +extern u32 current_vdd1_opp; +extern u32 current_vdd2_opp; + +/* + * Smartreflex module enable/disable interface. + * NOTE: if smartreflex is not enabled from sysfs, these functions will not + * do anything. + */ +#ifdef CONFIG_OMAP_SMARTREFLEX +void enable_smartreflex(u8 srid); +void disable_smartreflex(u8 srid); +int sr_voltage_set(u32 target_opp, u32 current_opp, + u8 target_vsel, u8 current_vsel); +int sr_vp_disable_both(u32 target_opp, u32 current_opp); +int sr_vp_enable_both(u32 target_opp, u32 current_opp); +int vc_send_command(u8 slave_addr, u8 reg_addr, u8 data, u16 *timeout_us); +int omap_pmic_srinit(void); +u32 omap_pmic_voltage_ramp_delay(u8 srid, u8 target_vsel, u8 current_vsel); +#ifdef CONFIG_OMAP_VC_BYPASS_UPDATE +int omap_pmic_voltage_cmds(u8 srid, u8 target_vsel); +#endif + +#ifdef CONFIG_PM_DEBUG +#define __SR_DEBUG +#endif + +#else +static inline void enable_smartreflex(u8 srid) +{ +} + +static inline void disable_smartreflex(u8 srid) +{ +} + +static inline int sr_voltage_set(u32 target_opp, u32 current_opp, + u8 target_vsel, u8 current_vsel) +{ + return -EINVAL; +} + +static inline int sr_vp_disable_both(u32 target_opp, u32 current_opp) +{ + return -EINVAL; +} + +static inline int sr_vp_enable_both(u32 target_opp, u32 current_opp) +{ + return -EINVAL; +} + +static inline int vc_send_command(u8 slave_addr, u8 reg_addr, u8 data, + u16 *timeout_us) +{ + return -EINVAL; +} + +#ifdef CONFIG_OMAP_VC_BYPASS_UPDATE +static inline int omap_pmic_voltage_cmds(u8 srid, u8 target_vsel) +{ + return -EINVAL; +} +#endif +#endif + +#ifdef __SR_DEBUG +int sr_debugfs_create_entries(struct dentry *d); +#else +static inline int sr_debugfs_create_entries(struct dentry *d) +{ + return -EINVAL; +} +#endif + +#endif /* __ARCH_ARM_MACH_OMAP3_SMARTREFLEX_H */ diff --git a/arch/arm/plat-omap/Kconfig b/arch/arm/plat-omap/Kconfig index 2143db5..6a3302c 100644 --- a/arch/arm/plat-omap/Kconfig +++ b/arch/arm/plat-omap/Kconfig @@ -81,19 +81,14 @@ config OMAP_SMARTREFLEX compensation for VDD1 and VDD2, user must write 1 to /sys/power/sr_vddX_autocomp, where X is 1 or 2. -config OMAP_SMARTREFLEX_TESTING - bool "Smartreflex testing support" +config OMAP_VC_BYPASS_UPDATE + bool "Use VC Bypass method of updating voltage" depends on OMAP_SMARTREFLEX default n help - Say Y if you want to enable SmartReflex testing with SW hardcoded - NVALUES intead of E-fuse NVALUES set in factory silicon testing. - - In some devices the E-fuse values have not been set, even though - SmartReflex modules are included. Using these hardcoded values set - in software, one can test the SmartReflex features without E-fuse. - - WARNING: Enabling this option may cause your device to hang! + Say Y if you would like to use VC Bypass method of + update. If this is disabled, the default + VP forceupdate method is used. config OMAP_RESET_CLOCKS bool "Reset unused clocks during boot"