msm: rpm-regulator: Add support for TCXO workaround
Certain NLDO type PMIC regulators require that the CXO oscillator
input into the PMIC (either CXO or TCXO depending upon the board)
is enabled in order to turn on successfully. The RPM is able to
handle this requirement for platforms which utilize a CXO
oscillator but not on platforms which use a TCXO oscillator.
This is because the TCXO oscillator cannot be disabled within its
4 ms warmup period. Any TCXO hardware disable attempted during
the warmup period will be ignored and will result in the TCXO
being latched on until another disable is attempted which has
sufficient delay.
Add conditional CXO clock resource enable and disable calls
around regulator requests which are sent to the RPM for affected
regulators on systems using a TCXO. The CXO disable calls should
be made from a delayed work queue task which is scheduled to run
4 ms after CXO is enabled.
Change-Id: I4093eeff3076bf5e6907593ed23f0bd2844f377e
Signed-off-by: David Collins <collinsd@codeaurora.org>
diff --git a/arch/arm/mach-msm/rpm-regulator.c b/arch/arm/mach-msm/rpm-regulator.c
index f663695..68ff55b 100644
--- a/arch/arm/mach-msm/rpm-regulator.c
+++ b/arch/arm/mach-msm/rpm-regulator.c
@@ -14,6 +14,7 @@
#define pr_fmt(fmt) "%s: " fmt, __func__
#include <linux/module.h>
+#include <linux/clk.h>
#include <linux/err.h>
#include <linux/kernel.h>
#include <linux/init.h>
@@ -21,7 +22,10 @@
#include <linux/spinlock.h>
#include <linux/string.h>
#include <linux/platform_device.h>
+#include <linux/wakelock.h>
+#include <linux/workqueue.h>
#include <linux/regulator/driver.h>
+
#include <mach/rpm.h>
#include <mach/rpm-regulator.h>
#include <mach/rpm-regulator-smd.h>
@@ -74,6 +78,11 @@
((_vreg->req[_vreg->part->_part.word].value & _vreg->part->_part.mask) \
>> _vreg->part->_part.shift)
+#define GET_PART_PREV_ACT(_vreg, _part) \
+ ((_vreg->prev_active_req[_vreg->part->_part.word].value \
+ & _vreg->part->_part.mask) \
+ >> _vreg->part->_part.shift)
+
#define USES_PART(_vreg, _part) (_vreg->part->_part.mask)
#define vreg_err(vreg, fmt, ...) \
@@ -287,6 +296,95 @@
vreg->req[0].id, vreg->req[0].value);
}
+static bool requires_tcxo_workaround;
+static bool tcxo_workaround_noirq;
+static struct clk *tcxo_handle;
+static struct wake_lock tcxo_wake_lock;
+static DEFINE_MUTEX(tcxo_mutex);
+/* Spin lock needed for sleep-selectable regulators. */
+static DEFINE_SPINLOCK(tcxo_noirq_lock);
+static bool tcxo_is_enabled;
+/*
+ * TCXO must be kept on for at least the duration of its warmup (4 ms);
+ * otherwise, it will stay on when hardware disabling is attempted.
+ */
+#define TCXO_WARMUP_TIME_MS 4
+
+static void tcxo_get_handle(void)
+{
+ int rc;
+
+ if (!tcxo_handle) {
+ tcxo_handle = clk_get_sys("rpm-regulator", "vref_buff");
+ if (IS_ERR(tcxo_handle)) {
+ tcxo_handle = NULL;
+ } else {
+ rc = clk_prepare(tcxo_handle);
+ if (rc) {
+ clk_put(tcxo_handle);
+ tcxo_handle = NULL;
+ }
+ }
+ }
+}
+
+/*
+ * Perform best effort enable of CXO. Since the MSM clock drivers depend upon
+ * the rpm-regulator driver, any rpm-regulator devices that are configured with
+ * always_on == 1 will not be able to enable CXO during probe. This does not
+ * cause a problem though since CXO will be enabled by the boot loaders before
+ * Apps boots up.
+ */
+static bool tcxo_enable(void)
+{
+ int rc;
+
+ if (tcxo_handle && !tcxo_is_enabled) {
+ rc = clk_enable(tcxo_handle);
+ if (!rc) {
+ tcxo_is_enabled = true;
+ wake_lock(&tcxo_wake_lock);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static void tcxo_delayed_disable_work(struct work_struct *work)
+{
+ unsigned long flags = 0;
+
+ if (tcxo_workaround_noirq)
+ spin_lock_irqsave(&tcxo_noirq_lock, flags);
+ else
+ mutex_lock(&tcxo_mutex);
+
+ clk_disable(tcxo_handle);
+ tcxo_is_enabled = false;
+ wake_unlock(&tcxo_wake_lock);
+
+ if (tcxo_workaround_noirq)
+ spin_unlock_irqrestore(&tcxo_noirq_lock, flags);
+ else
+ mutex_unlock(&tcxo_mutex);
+}
+
+static DECLARE_DELAYED_WORK(tcxo_disable_work, tcxo_delayed_disable_work);
+
+static void tcxo_delayed_disable(void)
+{
+ /*
+ * The delay in jiffies has 1 added to it to ensure that at least
+ * one jiffy takes place before the work is enqueued. Without this,
+ * the work would be scheduled to run in the very next jiffy which could
+ * result in too little delay and TCXO being stuck on.
+ */
+ if (tcxo_handle)
+ schedule_delayed_work(&tcxo_disable_work,
+ msecs_to_jiffies(TCXO_WARMUP_TIME_MS) + 1);
+}
+
/* Spin lock needed for sleep-selectable regulators. */
static DEFINE_SPINLOCK(rpm_noirq_lock);
@@ -321,6 +419,9 @@
{
struct msm_rpm_iv_pair *prev_req;
int rc = 0, max_uV_vote = 0;
+ unsigned long flags = 0;
+ bool tcxo_enabled = false;
+ bool voltage_increased = false;
unsigned prev0, prev1;
int *min_uV_vote;
int i;
@@ -362,6 +463,16 @@
/* Ignore duplicate requests */
if (vreg->req[0].value != prev_req[0].value ||
vreg->req[1].value != prev_req[1].value) {
+
+ /* Enable CXO clock if necessary for TCXO workaround. */
+ if (requires_tcxo_workaround && vreg->requires_cxo
+ && (set == MSM_RPM_CTX_SET_0)
+ && (GET_PART(vreg, uV) > GET_PART_PREV_ACT(vreg, uV))) {
+ voltage_increased = true;
+ spin_lock_irqsave(&tcxo_noirq_lock, flags);
+ tcxo_enabled = tcxo_enable();
+ }
+
rc = msm_rpmrs_set_noirq(set, vreg->req, cnt);
if (rc) {
vreg->req[0].value = prev0;
@@ -381,6 +492,16 @@
prev_req[0].value = vreg->req[0].value;
prev_req[1].value = vreg->req[1].value;
}
+
+ /*
+ * Schedule CXO clock to be disabled after TCXO warmup time if
+ * TCXO workaround is applicable for this regulator.
+ */
+ if (voltage_increased) {
+ if (tcxo_enabled)
+ tcxo_delayed_disable();
+ spin_unlock_irqrestore(&tcxo_noirq_lock, flags);
+ }
} else if (msm_rpm_vreg_debug_mask & MSM_RPM_VREG_DEBUG_DUPLICATE) {
rpm_regulator_duplicate(vreg, set, cnt);
}
@@ -916,6 +1037,9 @@
unsigned mask1, unsigned val1, unsigned cnt)
{
unsigned prev0 = 0, prev1 = 0;
+ unsigned long flags = 0;
+ bool tcxo_enabled = false;
+ bool voltage_increased = false;
int rc;
/*
@@ -942,7 +1066,25 @@
return 0;
}
- rc = msm_rpm_set(MSM_RPM_CTX_SET_0, vreg->req, cnt);
+ /* Enable CXO clock if necessary for TCXO workaround. */
+ if (requires_tcxo_workaround && vreg->requires_cxo
+ && (GET_PART(vreg, uV) > GET_PART_PREV_ACT(vreg, uV))) {
+ if (!tcxo_handle)
+ tcxo_get_handle();
+ if (tcxo_workaround_noirq)
+ spin_lock_irqsave(&tcxo_noirq_lock, flags);
+ else
+ mutex_lock(&tcxo_mutex);
+
+ voltage_increased = true;
+ tcxo_enabled = tcxo_enable();
+ }
+
+ if (voltage_increased && tcxo_workaround_noirq)
+ rc = msm_rpmrs_set_noirq(MSM_RPM_CTX_SET_0, vreg->req, cnt);
+ else
+ rc = msm_rpm_set(MSM_RPM_CTX_SET_0, vreg->req, cnt);
+
if (rc) {
vreg->req[0].value = prev0;
vreg->req[1].value = prev1;
@@ -956,6 +1098,20 @@
vreg->prev_active_req[1].value = vreg->req[1].value;
}
+ /*
+ * Schedule CXO clock to be disabled after TCXO warmup time if TCXO
+ * workaround is applicable for this regulator.
+ */
+ if (voltage_increased) {
+ if (tcxo_enabled)
+ tcxo_delayed_disable();
+
+ if (tcxo_workaround_noirq)
+ spin_unlock_irqrestore(&tcxo_noirq_lock, flags);
+ else
+ mutex_unlock(&tcxo_mutex);
+ }
+
return rc;
}
@@ -1470,6 +1626,20 @@
[RPM_REGULATOR_TYPE_CORNER] = &corner_ops,
};
+static struct vreg *rpm_vreg_get_vreg(int id)
+{
+ struct vreg *vreg;
+
+ if (id < config->vreg_id_min || id > config->vreg_id_max)
+ return NULL;
+
+ if (!config->is_real_id(id))
+ id = config->pc_id_to_real_id(id);
+ vreg = &config->vregs[id];
+
+ return vreg;
+}
+
static int __devinit
rpm_vreg_init_regulator(const struct rpm_regulator_init_data *pdata,
struct device *dev)
@@ -1478,7 +1648,7 @@
struct regulator_dev *rdev;
struct vreg *vreg;
unsigned pin_ctrl;
- int id, pin_fn;
+ int pin_fn;
int rc = 0;
if (!pdata) {
@@ -1486,17 +1656,12 @@
return -EINVAL;
}
- id = pdata->id;
-
- if (id < config->vreg_id_min || id > config->vreg_id_max) {
- pr_err("invalid regulator id: %d\n", id);
+ vreg = rpm_vreg_get_vreg(pdata->id);
+ if (!vreg) {
+ pr_err("invalid regulator id: %d\n", pdata->id);
return -ENODEV;
}
- if (!config->is_real_id(pdata->id))
- id = config->pc_id_to_real_id(pdata->id);
- vreg = &config->vregs[id];
-
if (config->is_real_id(pdata->id))
rdesc = &vreg->rdesc;
else
@@ -1627,6 +1792,7 @@
struct rpm_regulator_platform_data *platform_data;
static struct rpm_regulator_consumer_mapping *prev_consumer_map;
static int prev_consumer_map_len;
+ struct vreg *vreg;
int rc = 0;
int i, id;
@@ -1705,6 +1871,25 @@
}
+ if (platform_data->requires_tcxo_workaround
+ && !requires_tcxo_workaround) {
+ requires_tcxo_workaround = true;
+ wake_lock_init(&tcxo_wake_lock, WAKE_LOCK_SUSPEND,
+ "rpm_regulator_tcxo");
+ }
+
+ if (requires_tcxo_workaround && !tcxo_workaround_noirq) {
+ for (i = 0; i < platform_data->num_regulators; i++) {
+ vreg = rpm_vreg_get_vreg(
+ platform_data->init_data[i].id);
+ if (vreg && vreg->requires_cxo
+ && platform_data->init_data[i].sleep_selectable) {
+ tcxo_workaround_noirq = true;
+ break;
+ }
+ }
+ }
+
/* Initialize all of the regulators listed in the platform data. */
for (i = 0; i < platform_data->num_regulators; i++) {
rc = rpm_vreg_init_regulator(&platform_data->init_data[i],
@@ -1785,6 +1970,9 @@
for (i = 0; i < config->vregs_len; i++)
mutex_destroy(&config->vregs[i].pc_lock);
+
+ if (tcxo_handle)
+ clk_put(tcxo_handle);
}
postcore_initcall(rpm_vreg_init);