blob: e83f17af2857aaa1b0d42fac6f7f06cfdb666b52 [file] [log] [blame]
/*
* Copyright (c) 2012, 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/delay.h>
#include <linux/err.h>
#include <linux/remote_spinlock.h>
#include <mach/scm-io.h>
#include <mach/msm_iomap.h>
#include "clock.h"
#include "clock-pll.h"
#include "smd_private.h"
#ifdef CONFIG_MSM_SECURE_IO
#undef readl_relaxed
#undef writel_relaxed
#define readl_relaxed secure_readl
#define writel_relaxed secure_writel
#endif
#define PLL_OUTCTRL BIT(0)
#define PLL_BYPASSNL BIT(1)
#define PLL_RESET_N BIT(2)
#define PLL_MODE_MASK BM(3, 0)
#define PLL_EN_REG(x) ((x)->base ? (*(x)->base + (u32)((x)->en_reg)) : \
((x)->en_reg))
#define PLL_STATUS_REG(x) ((x)->base ? (*(x)->base + (u32)((x)->status_reg)) : \
((x)->status_reg))
#define PLL_MODE_REG(x) ((x)->base ? (*(x)->base + (u32)((x)->mode_reg)) : \
((x)->mode_reg))
static DEFINE_SPINLOCK(pll_reg_lock);
int pll_vote_clk_enable(struct clk *clk)
{
u32 ena;
unsigned long flags;
struct pll_vote_clk *pll = to_pll_vote_clk(clk);
spin_lock_irqsave(&pll_reg_lock, flags);
ena = readl_relaxed(PLL_EN_REG(pll));
ena |= pll->en_mask;
writel_relaxed(ena, PLL_EN_REG(pll));
spin_unlock_irqrestore(&pll_reg_lock, flags);
/* Wait until PLL is enabled */
while ((readl_relaxed(PLL_STATUS_REG(pll)) & pll->status_mask) == 0)
cpu_relax();
return 0;
}
void pll_vote_clk_disable(struct clk *clk)
{
u32 ena;
unsigned long flags;
struct pll_vote_clk *pll = to_pll_vote_clk(clk);
spin_lock_irqsave(&pll_reg_lock, flags);
ena = readl_relaxed(PLL_EN_REG(pll));
ena &= ~(pll->en_mask);
writel_relaxed(ena, PLL_EN_REG(pll));
spin_unlock_irqrestore(&pll_reg_lock, flags);
}
struct clk *pll_vote_clk_get_parent(struct clk *clk)
{
struct pll_vote_clk *pll = to_pll_vote_clk(clk);
return pll->parent;
}
int pll_vote_clk_is_enabled(struct clk *clk)
{
struct pll_vote_clk *pll = to_pll_vote_clk(clk);
return !!(readl_relaxed(PLL_STATUS_REG(pll)) & pll->status_mask);
}
struct clk_ops clk_ops_pll_vote = {
.enable = pll_vote_clk_enable,
.disable = pll_vote_clk_disable,
.auto_off = pll_vote_clk_disable,
.is_enabled = pll_vote_clk_is_enabled,
.get_parent = pll_vote_clk_get_parent,
};
static void __pll_clk_enable_reg(void __iomem *mode_reg)
{
u32 mode = readl_relaxed(mode_reg);
/* Disable PLL bypass mode. */
mode |= PLL_BYPASSNL;
writel_relaxed(mode, mode_reg);
/*
* H/W requires a 5us delay between disabling the bypass and
* de-asserting the reset. Delay 10us just to be safe.
*/
mb();
udelay(10);
/* De-assert active-low PLL reset. */
mode |= PLL_RESET_N;
writel_relaxed(mode, mode_reg);
/* Wait until PLL is locked. */
mb();
udelay(50);
/* Enable PLL output. */
mode |= PLL_OUTCTRL;
writel_relaxed(mode, mode_reg);
/* Ensure that the write above goes through before returning. */
mb();
}
static int local_pll_clk_enable(struct clk *clk)
{
unsigned long flags;
struct pll_clk *pll = to_pll_clk(clk);
spin_lock_irqsave(&pll_reg_lock, flags);
__pll_clk_enable_reg(PLL_MODE_REG(pll));
spin_unlock_irqrestore(&pll_reg_lock, flags);
return 0;
}
static void __pll_clk_disable_reg(void __iomem *mode_reg)
{
u32 mode = readl_relaxed(mode_reg);
mode &= ~PLL_MODE_MASK;
writel_relaxed(mode, mode_reg);
}
static void local_pll_clk_disable(struct clk *clk)
{
unsigned long flags;
struct pll_clk *pll = to_pll_clk(clk);
/*
* Disable the PLL output, disable test mode, enable
* the bypass mode, and assert the reset.
*/
spin_lock_irqsave(&pll_reg_lock, flags);
__pll_clk_disable_reg(PLL_MODE_REG(pll));
spin_unlock_irqrestore(&pll_reg_lock, flags);
}
static struct clk *local_pll_clk_get_parent(struct clk *clk)
{
struct pll_clk *pll = to_pll_clk(clk);
return pll->parent;
}
int sr_pll_clk_enable(struct clk *clk)
{
u32 mode;
unsigned long flags;
struct pll_clk *pll = to_pll_clk(clk);
spin_lock_irqsave(&pll_reg_lock, flags);
mode = readl_relaxed(PLL_MODE_REG(pll));
/* De-assert active-low PLL reset. */
mode |= PLL_RESET_N;
writel_relaxed(mode, PLL_MODE_REG(pll));
/*
* H/W requires a 5us delay between disabling the bypass and
* de-asserting the reset. Delay 10us just to be safe.
*/
mb();
udelay(10);
/* Disable PLL bypass mode. */
mode |= PLL_BYPASSNL;
writel_relaxed(mode, PLL_MODE_REG(pll));
/* Wait until PLL is locked. */
mb();
udelay(60);
/* Enable PLL output. */
mode |= PLL_OUTCTRL;
writel_relaxed(mode, PLL_MODE_REG(pll));
/* Ensure that the write above goes through before returning. */
mb();
spin_unlock_irqrestore(&pll_reg_lock, flags);
return 0;
}
struct clk_ops clk_ops_local_pll = {
.enable = local_pll_clk_enable,
.disable = local_pll_clk_disable,
.auto_off = local_pll_clk_disable,
.get_parent = local_pll_clk_get_parent,
};
struct pll_rate {
unsigned int lvalue;
unsigned long rate;
};
static struct pll_rate pll_l_rate[] = {
{10, 196000000},
{12, 245760000},
{30, 589820000},
{38, 737280000},
{41, 800000000},
{50, 960000000},
{52, 1008000000},
{62, 1200000000},
{63, 1209600000},
{0, 0},
};
#define PLL_BASE 7
struct shared_pll_control {
uint32_t version;
struct {
/*
* Denotes if the PLL is ON. Technically, this can be read
* directly from the PLL registers, but this feild is here,
* so let's use it.
*/
uint32_t on;
/*
* One bit for each processor core. The application processor
* is allocated bit position 1. All other bits should be
* considered as votes from other processors.
*/
uint32_t votes;
} pll[PLL_BASE + PLL_END];
};
static remote_spinlock_t pll_lock;
static struct shared_pll_control *pll_control;
void __init msm_shared_pll_control_init(void)
{
#define PLL_REMOTE_SPINLOCK_ID "S:7"
unsigned smem_size;
remote_spin_lock_init(&pll_lock, PLL_REMOTE_SPINLOCK_ID);
pll_control = smem_get_entry(SMEM_CLKREGIM_SOURCES, &smem_size);
if (!pll_control) {
pr_err("Can't find shared PLL control data structure!\n");
BUG();
/*
* There might be more PLLs than what the application processor knows
* about. But the index used for each PLL is guaranteed to remain the
* same.
*/
} else if (smem_size < sizeof(struct shared_pll_control)) {
pr_err("Shared PLL control data"
"structure too small!\n");
BUG();
} else if (pll_control->version != 0xCCEE0001) {
pr_err("Shared PLL control version mismatch!\n");
BUG();
} else {
pr_info("Shared PLL control available.\n");
return;
}
}
static int pll_clk_enable(struct clk *clk)
{
struct pll_shared_clk *pll = to_pll_shared_clk(clk);
unsigned int pll_id = pll->id;
remote_spin_lock(&pll_lock);
pll_control->pll[PLL_BASE + pll_id].votes |= BIT(1);
if (!pll_control->pll[PLL_BASE + pll_id].on) {
__pll_clk_enable_reg(PLL_MODE_REG(pll));
pll_control->pll[PLL_BASE + pll_id].on = 1;
}
remote_spin_unlock(&pll_lock);
return 0;
}
static void pll_clk_disable(struct clk *clk)
{
struct pll_shared_clk *pll = to_pll_shared_clk(clk);
unsigned int pll_id = pll->id;
remote_spin_lock(&pll_lock);
pll_control->pll[PLL_BASE + pll_id].votes &= ~BIT(1);
if (pll_control->pll[PLL_BASE + pll_id].on
&& !pll_control->pll[PLL_BASE + pll_id].votes) {
__pll_clk_disable_reg(PLL_MODE_REG(pll));
pll_control->pll[PLL_BASE + pll_id].on = 0;
}
remote_spin_unlock(&pll_lock);
}
static int pll_clk_is_enabled(struct clk *clk)
{
struct pll_shared_clk *pll = to_pll_shared_clk(clk);
return readl_relaxed(PLL_MODE_REG(pll)) & BIT(0);
}
static enum handoff pll_clk_handoff(struct clk *clk)
{
struct pll_shared_clk *pll = to_pll_shared_clk(clk);
unsigned int pll_lval;
struct pll_rate *l;
/*
* Wait for the PLLs to be initialized and then read their frequency.
*/
do {
pll_lval = readl_relaxed(PLL_MODE_REG(pll) + 4) & 0x3ff;
cpu_relax();
udelay(50);
} while (pll_lval == 0);
/* Convert PLL L values to PLL Output rate */
for (l = pll_l_rate; l->rate != 0; l++) {
if (l->lvalue == pll_lval) {
clk->rate = l->rate;
break;
}
}
if (!clk->rate) {
pr_crit("Unknown PLL's L value!\n");
BUG();
}
return HANDOFF_ENABLED_CLK;
}
struct clk_ops clk_pll_ops = {
.enable = pll_clk_enable,
.disable = pll_clk_disable,
.handoff = pll_clk_handoff,
.is_enabled = pll_clk_is_enabled,
};