mirror of https://github.com/Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
230 lines
5.4 KiB
230 lines
5.4 KiB
// SPDX-License-Identifier: GPL-2.0-only |
|
/* |
|
* Zynq PLL driver |
|
* |
|
* Copyright (C) 2013 Xilinx |
|
* |
|
* Sören Brinkmann <[email protected]> |
|
*/ |
|
#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 |
|
|
|
#define PLL_FBDIV_MIN 13 |
|
#define PLL_FBDIV_MAX 66 |
|
|
|
/** |
|
* 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 < PLL_FBDIV_MIN) |
|
fbdiv = PLL_FBDIV_MIN; |
|
else if (fbdiv > PLL_FBDIV_MAX) |
|
fbdiv = PLL_FBDIV_MAX; |
|
|
|
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 |
|
* @name: PLL name |
|
* @parent: Parent clock name |
|
* @pll_ctrl: Pointer to PLL control register |
|
* @pll_status: Pointer to PLL status register |
|
* @lock_index: Bit index to this PLL's lock status bit in @pll_status |
|
* @lock: Register lock |
|
* Returns handle to the registered clock. |
|
*/ |
|
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) |
|
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; |
|
}
|
|
|