blob: b5b6ca713c20a942245aeb60361d7b85c3bfec76 [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/socinfo.h>
#include <mach/msm_iomap.h>
#include "clock.h"
#include "clock-pll.h"
#include "smd_private.h"
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 void pll_enable(void __iomem *addr, unsigned on)
{
if (on) {
writel_relaxed(2, addr);
mb();
udelay(5);
writel_relaxed(6, addr);
mb();
udelay(50);
writel_relaxed(7, addr);
} else {
writel_relaxed(0, addr);
}
}
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_enable(pll->mode_reg, 1);
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_enable(pll->mode_reg, 0);
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) & BIT(0);
}
static bool pll_clk_is_local(struct clk *clk)
{
return true;
}
static int 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 + 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 0;
}
struct clk_ops clk_pll_ops = {
.enable = pll_clk_enable,
.disable = pll_clk_disable,
.handoff = pll_clk_handoff,
.is_local = pll_clk_is_local,
.is_enabled = pll_clk_is_enabled,
};