-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Refactor the PLL driver so it works with the clock controller driver. Signed-off-by: Soren Brinkmann <soren.brinkmann@xilinx.com> Signed-off-by: Michal Simek <michal.simek@xilinx.com> Acked-by: Mike Turquette <mturquette@linaro.org>
- Loading branch information
Soren Brinkmann
authored and
Michal Simek
committed
May 21, 2013
1 parent
c778879
commit 3682af4
Showing
1 changed file
with
235 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,235 @@ | ||
/* | ||
* Zynq PLL driver | ||
* | ||
* Copyright (C) 2013 Xilinx | ||
* | ||
* Sören Brinkmann <soren.brinkmann@xilinx.com> | ||
* | ||
* This program is free software: you can redistribute it and/or modify | ||
* it under the terms of the GNU General Public License v2 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. | ||
* | ||
* You should have received a copy of the GNU General Public License | ||
* along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
* | ||
*/ | ||
#include <linux/clk/zynq.h> | ||
#include <linux/clk-provider.h> | ||
#include <linux/slab.h> | ||
#include <linux/io.h> | ||
|
||
/** | ||
* struct zynq_pll | ||
* @hw: Handle between common and hardware-specific interfaces | ||
* @pll_ctrl: PLL control register | ||
* @pll_status: PLL status register | ||
* @lock: Register lock | ||
* @lockbit: Indicates the associated PLL_LOCKED bit in the PLL status | ||
* register. | ||
*/ | ||
struct zynq_pll { | ||
struct clk_hw hw; | ||
void __iomem *pll_ctrl; | ||
void __iomem *pll_status; | ||
spinlock_t *lock; | ||
u8 lockbit; | ||
}; | ||
#define to_zynq_pll(_hw) container_of(_hw, struct zynq_pll, hw) | ||
|
||
/* Register bitfield defines */ | ||
#define PLLCTRL_FBDIV_MASK 0x7f000 | ||
#define PLLCTRL_FBDIV_SHIFT 12 | ||
#define PLLCTRL_BPQUAL_MASK (1 << 3) | ||
#define PLLCTRL_PWRDWN_MASK 2 | ||
#define PLLCTRL_PWRDWN_SHIFT 1 | ||
#define PLLCTRL_RESET_MASK 1 | ||
#define PLLCTRL_RESET_SHIFT 0 | ||
|
||
/** | ||
* zynq_pll_round_rate() - Round a clock frequency | ||
* @hw: Handle between common and hardware-specific interfaces | ||
* @rate: Desired clock frequency | ||
* @prate: Clock frequency of parent clock | ||
* Returns frequency closest to @rate the hardware can generate. | ||
*/ | ||
static long zynq_pll_round_rate(struct clk_hw *hw, unsigned long rate, | ||
unsigned long *prate) | ||
{ | ||
u32 fbdiv; | ||
|
||
fbdiv = DIV_ROUND_CLOSEST(rate, *prate); | ||
if (fbdiv < 13) | ||
fbdiv = 13; | ||
else if (fbdiv > 66) | ||
fbdiv = 66; | ||
|
||
return *prate * fbdiv; | ||
} | ||
|
||
/** | ||
* zynq_pll_recalc_rate() - Recalculate clock frequency | ||
* @hw: Handle between common and hardware-specific interfaces | ||
* @parent_rate: Clock frequency of parent clock | ||
* Returns current clock frequency. | ||
*/ | ||
static unsigned long zynq_pll_recalc_rate(struct clk_hw *hw, | ||
unsigned long parent_rate) | ||
{ | ||
struct zynq_pll *clk = to_zynq_pll(hw); | ||
u32 fbdiv; | ||
|
||
/* | ||
* makes probably sense to redundantly save fbdiv in the struct | ||
* zynq_pll to save the IO access. | ||
*/ | ||
fbdiv = (readl(clk->pll_ctrl) & PLLCTRL_FBDIV_MASK) >> | ||
PLLCTRL_FBDIV_SHIFT; | ||
|
||
return parent_rate * fbdiv; | ||
} | ||
|
||
/** | ||
* zynq_pll_is_enabled - Check if a clock is enabled | ||
* @hw: Handle between common and hardware-specific interfaces | ||
* Returns 1 if the clock is enabled, 0 otherwise. | ||
* | ||
* Not sure this is a good idea, but since disabled means bypassed for | ||
* this clock implementation we say we are always enabled. | ||
*/ | ||
static int zynq_pll_is_enabled(struct clk_hw *hw) | ||
{ | ||
unsigned long flags = 0; | ||
u32 reg; | ||
struct zynq_pll *clk = to_zynq_pll(hw); | ||
|
||
spin_lock_irqsave(clk->lock, flags); | ||
|
||
reg = readl(clk->pll_ctrl); | ||
|
||
spin_unlock_irqrestore(clk->lock, flags); | ||
|
||
return !(reg & (PLLCTRL_RESET_MASK | PLLCTRL_PWRDWN_MASK)); | ||
} | ||
|
||
/** | ||
* zynq_pll_enable - Enable clock | ||
* @hw: Handle between common and hardware-specific interfaces | ||
* Returns 0 on success | ||
*/ | ||
static int zynq_pll_enable(struct clk_hw *hw) | ||
{ | ||
unsigned long flags = 0; | ||
u32 reg; | ||
struct zynq_pll *clk = to_zynq_pll(hw); | ||
|
||
if (zynq_pll_is_enabled(hw)) | ||
return 0; | ||
|
||
pr_info("PLL: enable\n"); | ||
|
||
/* Power up PLL and wait for lock */ | ||
spin_lock_irqsave(clk->lock, flags); | ||
|
||
reg = readl(clk->pll_ctrl); | ||
reg &= ~(PLLCTRL_RESET_MASK | PLLCTRL_PWRDWN_MASK); | ||
writel(reg, clk->pll_ctrl); | ||
while (!(readl(clk->pll_status) & (1 << clk->lockbit))) | ||
; | ||
|
||
spin_unlock_irqrestore(clk->lock, flags); | ||
|
||
return 0; | ||
} | ||
|
||
/** | ||
* zynq_pll_disable - Disable clock | ||
* @hw: Handle between common and hardware-specific interfaces | ||
* Returns 0 on success | ||
*/ | ||
static void zynq_pll_disable(struct clk_hw *hw) | ||
{ | ||
unsigned long flags = 0; | ||
u32 reg; | ||
struct zynq_pll *clk = to_zynq_pll(hw); | ||
|
||
if (!zynq_pll_is_enabled(hw)) | ||
return; | ||
|
||
pr_info("PLL: shutdown\n"); | ||
|
||
/* shut down PLL */ | ||
spin_lock_irqsave(clk->lock, flags); | ||
|
||
reg = readl(clk->pll_ctrl); | ||
reg |= PLLCTRL_RESET_MASK | PLLCTRL_PWRDWN_MASK; | ||
writel(reg, clk->pll_ctrl); | ||
|
||
spin_unlock_irqrestore(clk->lock, flags); | ||
} | ||
|
||
static const struct clk_ops zynq_pll_ops = { | ||
.enable = zynq_pll_enable, | ||
.disable = zynq_pll_disable, | ||
.is_enabled = zynq_pll_is_enabled, | ||
.round_rate = zynq_pll_round_rate, | ||
.recalc_rate = zynq_pll_recalc_rate | ||
}; | ||
|
||
/** | ||
* clk_register_zynq_pll() - Register PLL with the clock framework | ||
* @np Pointer to the DT device node | ||
*/ | ||
struct clk *clk_register_zynq_pll(const char *name, const char *parent, | ||
void __iomem *pll_ctrl, void __iomem *pll_status, u8 lock_index, | ||
spinlock_t *lock) | ||
{ | ||
struct zynq_pll *pll; | ||
struct clk *clk; | ||
u32 reg; | ||
const char *parent_arr[1] = {parent}; | ||
unsigned long flags = 0; | ||
struct clk_init_data initd = { | ||
.name = name, | ||
.parent_names = parent_arr, | ||
.ops = &zynq_pll_ops, | ||
.num_parents = 1, | ||
.flags = 0 | ||
}; | ||
|
||
pll = kmalloc(sizeof(*pll), GFP_KERNEL); | ||
if (!pll) { | ||
pr_err("%s: Could not allocate Zynq PLL clk.\n", __func__); | ||
return ERR_PTR(-ENOMEM); | ||
} | ||
|
||
/* Populate the struct */ | ||
pll->hw.init = &initd; | ||
pll->pll_ctrl = pll_ctrl; | ||
pll->pll_status = pll_status; | ||
pll->lockbit = lock_index; | ||
pll->lock = lock; | ||
|
||
spin_lock_irqsave(pll->lock, flags); | ||
|
||
reg = readl(pll->pll_ctrl); | ||
reg &= ~PLLCTRL_BPQUAL_MASK; | ||
writel(reg, pll->pll_ctrl); | ||
|
||
spin_unlock_irqrestore(pll->lock, flags); | ||
|
||
clk = clk_register(NULL, &pll->hw); | ||
if (WARN_ON(IS_ERR(clk))) | ||
goto free_pll; | ||
|
||
return clk; | ||
|
||
free_pll: | ||
kfree(pll); | ||
|
||
return clk; | ||
} |