Skip to content

Commit

Permalink
clk: zynqmp: Add the pll driver
Browse files Browse the repository at this point in the history
Add pll driver support.

Signed-off-by: Shubhrajyoti Datta <[email protected]>
Signed-off-by: Michal Simek <[email protected]>
  • Loading branch information
Shubhrajyoti Datta authored and Michal Simek committed Aug 22, 2016
1 parent e0d6538 commit ea2cd72
Show file tree
Hide file tree
Showing 3 changed files with 252 additions and 0 deletions.
3 changes: 3 additions & 0 deletions drivers/clk/zynqmp/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Zynq Ultrascale+ MPSoC clock specific Makefile

obj-$(CONFIG_ARCH_ZYNQMP) += pll.o
244 changes: 244 additions & 0 deletions drivers/clk/zynqmp/pll.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
/*
* Zynq UltraScale+ MPSoC PLL driver
*
* Copyright (C) 2016 Xilinx
*
* 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/zynqmp.h>
#include <linux/clk-provider.h>
#include <linux/slab.h>
#include <linux/io.h>

/**
* struct zynqmp_pll - Structure for PLL clock
* @hw: Handle between common and hardware-specific interfaces
* @pll_ctrl: PLL control register
* @pll_status: PLL status register
* @lockbit: Indicates the associated PLL_LOCKED bit in the PLL status
* register
*/
struct zynqmp_pll {
struct clk_hw hw;
resource_size_t *pll_ctrl;
resource_size_t *pll_status;
u8 lockbit;
};
#define to_zynqmp_pll(_hw) container_of(_hw, struct zynqmp_pll, hw)

/* Register bitfield defines */
#define PLLCTRL_FBDIV_MASK 0x7f00
#define PLLCTRL_FBDIV_SHIFT 8
#define PLLCTRL_BP_MASK (1 << 3)
#define PLLCTRL_DIV2_MASK (1 << 16)
#define PLLCTRL_RESET_MASK 1
#define PLLCTRL_RESET_VAL 1
#define PLL_STATUS_LOCKED 1
#define PLLCTRL_RESET_SHIFT 0
#define PLLCTRL_DIV2_SHIFT 16

#define PLL_FBDIV_MIN 25
#define PLL_FBDIV_MAX 125

enum pll_mode {
PLL_MODE_FRAC,
PLL_MODE_INT,
};

#define FRAC_OFFSET 0x8
#define PLLFCFG_FRAC_EN BIT(31)
#define FRAC_DIV 0x10000 /* 2^16 */

static inline enum pll_mode pll_frac_get_mode(struct clk_hw *hw)
{
struct zynqmp_pll *clk = to_zynqmp_pll(hw);
u32 reg;

reg = zynqmp_pm_mmio_readl(clk->pll_ctrl + FRAC_OFFSET);
reg = reg & PLLFCFG_FRAC_EN;
return reg ? PLL_MODE_FRAC : PLL_MODE_INT;
}

/**
* zynqmp_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
*
* Return: Frequency closest to @rate the hardware can generate
*/
static long zynqmp_pll_round_rate(struct clk_hw *hw, unsigned long rate,
unsigned long *prate)
{
u32 fbdiv;
long rate_div, frac, m, f;

if (pll_frac_get_mode(hw) == PLL_MODE_FRAC) {
rate_div = ((rate*100) / *prate);
m = rate_div / 100;
f = rate_div % 100;
m = clamp_t(u32, m, (PLL_FBDIV_MIN), (PLL_FBDIV_MAX));
rate = *prate * m;
frac = (*prate * f) / 100;
return (rate + frac);
}

fbdiv = DIV_ROUND_CLOSEST(rate, *prate);
fbdiv = clamp_t(u32, fbdiv, PLL_FBDIV_MIN, PLL_FBDIV_MAX);
return *prate * fbdiv;
}

/**
* zynqmp_pll_recalc_rate - Recalculate clock frequency
* @hw: Handle between common and hardware-specific interfaces
* @parent_rate: Clock frequency of parent clock
* Return: Current clock frequency
*/
static unsigned long zynqmp_pll_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
struct zynqmp_pll *clk = to_zynqmp_pll(hw);
u32 fbdiv, div2, data;
unsigned long rate, frac;

/*
* makes probably sense to redundantly save fbdiv in the struct
* zynqmp_pll to save the IO access.
*/
fbdiv = (zynqmp_pm_mmio_readl(clk->pll_ctrl) & PLLCTRL_FBDIV_MASK) >>
PLLCTRL_FBDIV_SHIFT;
div2 = (zynqmp_pm_mmio_readl(clk->pll_ctrl) & PLLCTRL_DIV2_MASK) >>
PLLCTRL_DIV2_SHIFT;
if (div2)
fbdiv = fbdiv * 2;

rate = parent_rate * fbdiv;
if (pll_frac_get_mode(hw) == PLL_MODE_FRAC) {
data = (zynqmp_pm_mmio_readl(clk->pll_ctrl + FRAC_OFFSET) &
0xffff);
frac = (rate * data) / FRAC_DIV;
rate = rate + frac;
}
return rate;
}

/**
* zynqmp_pll_is_enabled - Check if a clock is enabled
* @hw: Handle between common and hardware-specific interfaces
*
* Return: 1 if the clock is enabled, 0 otherwise
*/
static int zynqmp_pll_is_enabled(struct clk_hw *hw)
{
u32 reg;
struct zynqmp_pll *clk = to_zynqmp_pll(hw);

reg = zynqmp_pm_mmio_readl(clk->pll_ctrl);

return !(reg & (PLLCTRL_RESET_MASK));
}

/**
* zynqmp_pll_enable - Enable clock
* @hw: Handle between common and hardware-specific interfaces
*
* Return: 0 always
*/
static int zynqmp_pll_enable(struct clk_hw *hw)
{
u32 reg;
struct zynqmp_pll *clk = to_zynqmp_pll(hw);

if (zynqmp_pll_is_enabled(hw))
return 0;

pr_info("PLL: enable\n");

reg = zynqmp_pm_mmio_readl(clk->pll_ctrl);
reg &= ~(PLLCTRL_RESET_MASK);
zynqmp_pm_mmio_writel(reg, clk->pll_ctrl);
while (!(zynqmp_pm_mmio_readl(clk->pll_status) & (1 << clk->lockbit)))
cpu_relax();

return 0;
}

/**
* zynqmp_pll_disable - Disable clock
* @hw: Handle between common and hardware-specific interfaces
*
*/
static void zynqmp_pll_disable(struct clk_hw *hw)
{
struct zynqmp_pll *clk = to_zynqmp_pll(hw);

if (!zynqmp_pll_is_enabled(hw))
return;

pr_info("PLL: shutdown\n");

/* shut down PLL */
zynqmp_pm_mmio_write((u32)(ulong)clk->pll_ctrl, PLLCTRL_RESET_MASK,
PLLCTRL_RESET_VAL);
}

static const struct clk_ops zynqmp_pll_ops = {
.enable = zynqmp_pll_enable,
.disable = zynqmp_pll_disable,
.is_enabled = zynqmp_pll_is_enabled,
.round_rate = zynqmp_pll_round_rate,
.recalc_rate = zynqmp_pll_recalc_rate
};

/**
* clk_register_zynqmp_pll - Register PLL with the clock framework
* @name: PLL name
* @flag: PLL flags
* @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
*
* Return: Handle to the registered clock
*/
struct clk *clk_register_zynqmp_pll(const char *name, const char *parent,
unsigned long flag, resource_size_t *pll_ctrl,
resource_size_t *pll_status, u8 lock_index)
{
struct zynqmp_pll *pll;
struct clk *clk;
struct clk_init_data init;

init.name = name;
init.ops = &zynqmp_pll_ops;
init.flags = flag;
init.parent_names = &parent;
init.num_parents = 1;

pll = kmalloc(sizeof(*pll), GFP_KERNEL);
if (!pll)
return ERR_PTR(-ENOMEM);

/* Populate the struct */
pll->hw.init = &init;
pll->pll_ctrl = pll_ctrl;
pll->pll_status = pll_status;
pll->lockbit = lock_index;

clk = clk_register(NULL, &pll->hw);
if (WARN_ON(IS_ERR(clk)))
kfree(pll);

return clk;
}
5 changes: 5 additions & 0 deletions include/linux/clk/zynqmp.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,9 @@ static inline int zynqmp_pm_mmio_writel(u32 val, void __iomem *reg)
pr_err("Write failed\n");
return ret;
}

struct clk *clk_register_zynqmp_pll(const char *name, const char *parent,
unsigned long flag, resource_size_t *pll_ctrl,
resource_size_t *pll_status, u8 lock_index);

#endif

0 comments on commit ea2cd72

Please sign in to comment.