blob: 1e9c05fb98c118d5655f9a5139524c301efe02bf [file] [log] [blame]
/* Copyright (c) 2010-2011, Code Aurora Forum. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/err.h>
#include <linux/module.h>
#include <linux/spinlock.h>
#include <linux/debugfs.h>
#include <linux/list.h>
#include <linux/seq_file.h>
#include <mach/msm_xo.h>
#include <mach/rpm.h>
#include <mach/socinfo.h>
#include "rpm_resources.h"
static DEFINE_SPINLOCK(msm_xo_lock);
struct msm_xo {
unsigned votes[NUM_MSM_XO_MODES];
unsigned mode;
struct list_head voters;
};
struct msm_xo_voter {
const char *name;
unsigned mode;
struct msm_xo *xo;
struct list_head list;
};
static struct msm_xo msm_xo_sources[NUM_MSM_XO_IDS];
#ifdef CONFIG_DEBUG_FS
static const char *msm_xo_mode_to_str(unsigned mode)
{
switch (mode) {
case MSM_XO_MODE_ON:
return "ON";
case MSM_XO_MODE_PIN_CTRL:
return "PIN";
case MSM_XO_MODE_OFF:
return "OFF";
default:
return "ERR";
}
}
static void msm_xo_dump_xo(struct seq_file *m, struct msm_xo *xo,
const char *name)
{
struct msm_xo_voter *voter;
seq_printf(m, "%-20s%s\n", name, msm_xo_mode_to_str(xo->mode));
list_for_each_entry(voter, &xo->voters, list)
seq_printf(m, " %s %-16s %s\n",
xo->mode == voter->mode ? "*" : " ",
voter->name,
msm_xo_mode_to_str(voter->mode));
}
static int msm_xo_show_voters(struct seq_file *m, void *v)
{
unsigned long flags;
spin_lock_irqsave(&msm_xo_lock, flags);
msm_xo_dump_xo(m, &msm_xo_sources[MSM_XO_TCXO_D0], "TCXO D0");
msm_xo_dump_xo(m, &msm_xo_sources[MSM_XO_TCXO_D1], "TCXO D1");
msm_xo_dump_xo(m, &msm_xo_sources[MSM_XO_TCXO_A0], "TCXO A0");
msm_xo_dump_xo(m, &msm_xo_sources[MSM_XO_TCXO_A1], "TCXO A1");
msm_xo_dump_xo(m, &msm_xo_sources[MSM_XO_TCXO_A2], "TCXO A2");
msm_xo_dump_xo(m, &msm_xo_sources[MSM_XO_CORE], "TCXO Core");
msm_xo_dump_xo(m, &msm_xo_sources[MSM_XO_PXO], "PXO during sleep");
spin_unlock_irqrestore(&msm_xo_lock, flags);
return 0;
}
static int msm_xo_voters_open(struct inode *inode, struct file *file)
{
return single_open(file, msm_xo_show_voters, inode->i_private);
}
static const struct file_operations msm_xo_voters_ops = {
.open = msm_xo_voters_open,
.read = seq_read,
.llseek = seq_lseek,
.release = seq_release,
};
static int __init msm_xo_debugfs_init(void)
{
struct dentry *entry;
entry = debugfs_create_file("xo_voters", S_IRUGO, NULL, NULL,
&msm_xo_voters_ops);
return IS_ERR(entry) ? PTR_ERR(entry) : 0;
}
late_initcall(msm_xo_debugfs_init);
#endif
static int msm_xo_update_vote(struct msm_xo *xo)
{
int ret;
unsigned vote, prev_vote = xo->mode;
struct msm_rpm_iv_pair cmd;
if (xo->votes[MSM_XO_MODE_ON])
vote = MSM_XO_MODE_ON;
else if (xo->votes[MSM_XO_MODE_PIN_CTRL])
vote = MSM_XO_MODE_PIN_CTRL;
else
vote = MSM_XO_MODE_OFF;
if (vote == prev_vote)
return 0;
/*
* Change the vote here to simplify the TCXO logic. If the RPM
* command fails we'll rollback.
*/
xo->mode = vote;
if (xo == &msm_xo_sources[MSM_XO_PXO]) {
cmd.id = MSM_RPM_ID_PXO_CLK;
cmd.value = msm_xo_sources[MSM_XO_PXO].mode ? 1 : 0;
ret = msm_rpmrs_set_noirq(MSM_RPM_CTX_SET_SLEEP, &cmd, 1);
} else {
cmd.id = MSM_RPM_ID_CXO_BUFFERS;
cmd.value = (msm_xo_sources[MSM_XO_TCXO_D0].mode << 0) |
(msm_xo_sources[MSM_XO_TCXO_D1].mode << 8) |
(msm_xo_sources[MSM_XO_TCXO_A0].mode << 16) |
(msm_xo_sources[MSM_XO_TCXO_A1].mode << 24) |
(msm_xo_sources[MSM_XO_TCXO_A2].mode << 28) |
((msm_xo_sources[MSM_XO_CORE].mode ? 1 : 0) << 20);
ret = msm_rpm_set_noirq(MSM_RPM_CTX_SET_0, &cmd, 1);
}
if (ret)
xo->mode = prev_vote;
return ret;
}
static int __msm_xo_mode_vote(struct msm_xo_voter *xo_voter, unsigned mode)
{
int ret;
struct msm_xo *xo = xo_voter->xo;
if (xo_voter->mode == mode)
return 0;
xo->votes[mode]++;
xo->votes[xo_voter->mode]--;
ret = msm_xo_update_vote(xo);
if (ret) {
xo->votes[xo_voter->mode]++;
xo->votes[mode]--;
goto out;
}
xo_voter->mode = mode;
out:
return ret;
}
/**
* msm_xo_mode_vote() - Vote for an XO to be ON, OFF, or under PIN_CTRL
* @xo_voter - Valid handle returned from msm_xo_get()
* @mode - Mode to vote for (ON, OFF, PIN_CTRL)
*
* Vote for an XO to be either ON, OFF, or under PIN_CTRL. Votes are
* aggregated with ON taking precedence over PIN_CTRL taking precedence
* over OFF.
*
* This function returns 0 on success or a negative error code on failure.
*/
int msm_xo_mode_vote(struct msm_xo_voter *xo_voter, enum msm_xo_modes mode)
{
int ret;
unsigned long flags;
if (!xo_voter)
return 0;
if (mode >= NUM_MSM_XO_MODES || IS_ERR(xo_voter))
return -EINVAL;
spin_lock_irqsave(&msm_xo_lock, flags);
ret = __msm_xo_mode_vote(xo_voter, mode);
spin_unlock_irqrestore(&msm_xo_lock, flags);
return ret;
}
EXPORT_SYMBOL(msm_xo_mode_vote);
/**
* msm_xo_get() - Get a voting handle for an XO
* @xo_id - XO identifier
* @voter - Debug string to identify users
*
* XO voters vote for OFF by default. This function returns a pointer
* indicating success. An ERR_PTR is returned on failure.
*
* If XO voting is disabled, %NULL is returned.
*/
struct msm_xo_voter *msm_xo_get(enum msm_xo_ids xo_id, const char *voter)
{
int ret;
unsigned long flags;
struct msm_xo_voter *xo_voter;
/*
* TODO: Remove early return for 8064 once RPM XO voting support
* is available.
*/
if (cpu_is_apq8064())
return NULL;
if (xo_id >= NUM_MSM_XO_IDS) {
ret = -EINVAL;
goto err;
}
xo_voter = kzalloc(sizeof(*xo_voter), GFP_KERNEL);
if (!xo_voter) {
ret = -ENOMEM;
goto err;
}
xo_voter->name = kstrdup(voter, GFP_KERNEL);
if (!xo_voter->name) {
ret = -ENOMEM;
goto err_name;
}
xo_voter->xo = &msm_xo_sources[xo_id];
/* Voters vote for OFF by default */
spin_lock_irqsave(&msm_xo_lock, flags);
xo_voter->xo->votes[MSM_XO_MODE_OFF]++;
list_add(&xo_voter->list, &xo_voter->xo->voters);
spin_unlock_irqrestore(&msm_xo_lock, flags);
return xo_voter;
err_name:
kfree(xo_voter);
err:
return ERR_PTR(ret);
}
EXPORT_SYMBOL(msm_xo_get);
/**
* msm_xo_put() - Release a voting handle
* @xo_voter - Valid handle returned from msm_xo_get()
*
* Release a reference to an XO voting handle. This also removes the voter's
* vote, therefore calling msm_xo_mode_vote(xo_voter, MSM_XO_MODE_OFF)
* beforehand is unnecessary.
*/
void msm_xo_put(struct msm_xo_voter *xo_voter)
{
unsigned long flags;
if (!xo_voter || IS_ERR(xo_voter))
return;
spin_lock_irqsave(&msm_xo_lock, flags);
__msm_xo_mode_vote(xo_voter, MSM_XO_MODE_OFF);
xo_voter->xo->votes[MSM_XO_MODE_OFF]--;
list_del(&xo_voter->list);
spin_unlock_irqrestore(&msm_xo_lock, flags);
kfree(xo_voter->name);
kfree(xo_voter);
}
EXPORT_SYMBOL(msm_xo_put);
int __init msm_xo_init(void)
{
int i;
int ret;
struct msm_rpm_iv_pair cmd[2];
for (i = 0; i < ARRAY_SIZE(msm_xo_sources); i++)
INIT_LIST_HEAD(&msm_xo_sources[i].voters);
cmd[0].id = MSM_RPM_ID_PXO_CLK;
cmd[0].value = 1;
cmd[1].id = MSM_RPM_ID_CXO_BUFFERS;
cmd[1].value = 0;
ret = msm_rpmrs_set(MSM_RPM_CTX_SET_0, cmd, 2);
if (ret)
goto out;
cmd[0].id = MSM_RPM_ID_PXO_CLK;
cmd[0].value = 0;
ret = msm_rpmrs_set(MSM_RPM_CTX_SET_SLEEP, cmd, 1);
if (ret)
goto out;
out:
return ret;
}