1751 lines
46 KiB
C
Executable File
1751 lines
46 KiB
C
Executable File
/*
|
|
* Copyright (c) 2014 Samsung Electronics Co., Ltd.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
* This file contains the utility functions for composite clocks.
|
|
*/
|
|
|
|
#include <linux/syscore_ops.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/log2.h>
|
|
#include <linux/of.h>
|
|
#include <linux/delay.h>
|
|
#include <soc/samsung/cal-if.h>
|
|
|
|
#include "composite.h"
|
|
|
|
#define PLL_STAT_OSC (0x0)
|
|
#define PLL_STAT_PLL (0x1)
|
|
#define PLL_STAT_CHANGE (0x4)
|
|
#define PLL_STAT_MASK (0x7)
|
|
|
|
#define to_comp_pll(_hw) container_of(_hw, struct samsung_composite_pll, hw)
|
|
#define to_comp_mux(_hw) container_of(_hw, struct samsung_composite_mux, hw)
|
|
#define to_comp_divider(_hw) container_of(_hw, struct samsung_composite_divider, hw)
|
|
#define to_usermux(_hw) container_of(_hw, struct clk_samsung_usermux, hw)
|
|
#define to_vclk(_hw) container_of(_hw, struct samsung_vclk, hw)
|
|
|
|
static DEFINE_SPINLOCK(lock);
|
|
|
|
#ifdef CONFIG_PM_SLEEP
|
|
void samsung_clk_save(void __iomem *base,
|
|
struct samsung_clk_reg_dump *rd,
|
|
unsigned int num_regs)
|
|
{
|
|
for (; num_regs > 0; --num_regs, ++rd)
|
|
rd->value = readl(base + rd->offset);
|
|
}
|
|
|
|
void samsung_clk_restore(void __iomem *base,
|
|
const struct samsung_clk_reg_dump *rd,
|
|
unsigned int num_regs)
|
|
{
|
|
for (; num_regs > 0; --num_regs, ++rd)
|
|
writel(rd->value, base + rd->offset);
|
|
}
|
|
|
|
struct samsung_clk_reg_dump *samsung_clk_alloc_reg_dump(
|
|
const unsigned long *rdump,
|
|
unsigned long nr_rdump)
|
|
{
|
|
struct samsung_clk_reg_dump *rd;
|
|
unsigned int i;
|
|
|
|
rd = kcalloc(nr_rdump, sizeof(*rd), GFP_KERNEL);
|
|
if (!rd)
|
|
return NULL;
|
|
|
|
for (i = 0; i < nr_rdump; ++i)
|
|
rd[i].offset = rdump[i];
|
|
|
|
return rd;
|
|
}
|
|
#endif /* CONFIG_PM_SLEEP */
|
|
|
|
/* setup the essentials required to support clock lookup using ccf */
|
|
struct samsung_clk_provider *__init samsung_clk_init(struct device_node *np,
|
|
void __iomem *base, unsigned long nr_clks)
|
|
{
|
|
struct samsung_clk_provider *ctx = NULL;
|
|
struct clk **clk_table;
|
|
int i;
|
|
|
|
if (!np)
|
|
return ctx;
|
|
|
|
ctx = kzalloc(sizeof(struct samsung_clk_provider), GFP_KERNEL);
|
|
if (!ctx)
|
|
panic("could not allocate clock provider context.\n");
|
|
|
|
clk_table = kcalloc(nr_clks, sizeof(struct clk *), GFP_KERNEL);
|
|
if (!clk_table)
|
|
panic("could not allocate clock lookup table\n");
|
|
|
|
for (i = 0; i < nr_clks; ++i)
|
|
clk_table[i] = ERR_PTR(-ENOENT);
|
|
|
|
ctx->reg_base = base;
|
|
ctx->clk_data.clks = clk_table;
|
|
ctx->clk_data.clk_num = nr_clks;
|
|
spin_lock_init(&ctx->lock);
|
|
|
|
return ctx;
|
|
}
|
|
|
|
void __init samsung_clk_of_add_provider(struct device_node *np,
|
|
struct samsung_clk_provider *ctx)
|
|
{
|
|
if (np) {
|
|
if (of_clk_add_provider(np, of_clk_src_onecell_get,
|
|
&ctx->clk_data))
|
|
panic("could not register clk provider\n");
|
|
}
|
|
}
|
|
|
|
/* add a clock instance to the clock lookup table used for dt based lookup */
|
|
static void samsung_clk_add_lookup(struct samsung_clk_provider *ctx, struct clk *clk,
|
|
unsigned int id)
|
|
{
|
|
if (ctx->clk_data.clks && id)
|
|
ctx->clk_data.clks[id] = clk;
|
|
}
|
|
|
|
/* register a list of fixed clocks */
|
|
void __init samsung_register_fixed_rate(struct samsung_clk_provider *ctx,
|
|
struct samsung_fixed_rate *list, unsigned int nr_clk)
|
|
{
|
|
struct clk *clk;
|
|
unsigned int idx, ret;
|
|
|
|
for (idx = 0; idx < nr_clk; idx++, list++) {
|
|
clk = clk_register_fixed_rate(NULL, list->name,
|
|
list->parent_name, list->flags, list->fixed_rate);
|
|
if (IS_ERR(clk)) {
|
|
pr_err("%s: failed to register clock %s\n", __func__,
|
|
list->name);
|
|
continue;
|
|
}
|
|
|
|
samsung_clk_add_lookup(ctx, clk, list->id);
|
|
|
|
/*
|
|
* Unconditionally add a clock lookup for the fixed rate clocks.
|
|
* There are not many of these on any of Samsung platforms.
|
|
*/
|
|
ret = clk_register_clkdev(clk, list->name, NULL);
|
|
if (ret)
|
|
pr_err("%s: failed to register clock lookup for %s",
|
|
__func__, list->name);
|
|
}
|
|
}
|
|
|
|
/* register a list of fixed factor clocks */
|
|
void __init samsung_register_fixed_factor(struct samsung_clk_provider *ctx,
|
|
struct samsung_fixed_factor *list, unsigned int nr_clk)
|
|
{
|
|
struct clk *clk;
|
|
unsigned int idx, ret;
|
|
|
|
for (idx = 0; idx < nr_clk; idx++, list++) {
|
|
clk = clk_register_fixed_factor(NULL, list->name,
|
|
list->parent_name, list->flags, list->mult, list->div);
|
|
if (IS_ERR(clk)) {
|
|
pr_err("%s: failed to register clock %s\n", __func__,
|
|
list->name);
|
|
continue;
|
|
}
|
|
|
|
samsung_clk_add_lookup(ctx, clk, list->id);
|
|
|
|
ret = clk_register_clkdev(clk, list->name, NULL);
|
|
if (ret)
|
|
pr_err("%s: failed to register clock lookup for %s",
|
|
__func__, list->name);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* obtain the clock speed of all external fixed clock sources from device
|
|
* tree and register it
|
|
*/
|
|
void __init samsung_register_of_fixed_ext(struct samsung_clk_provider *ctx,
|
|
struct samsung_fixed_rate *fixed_rate_clk,
|
|
unsigned int nr_fixed_rate_clk,
|
|
struct of_device_id *clk_matches)
|
|
{
|
|
const struct of_device_id *match;
|
|
struct device_node *np;
|
|
u32 freq;
|
|
|
|
for_each_matching_node_and_match(np, clk_matches, &match) {
|
|
if (of_property_read_u32(np, "clock-frequency", &freq))
|
|
continue;
|
|
fixed_rate_clk[(unsigned long)match->data].fixed_rate = freq;
|
|
}
|
|
samsung_register_fixed_rate(ctx, fixed_rate_clk, nr_fixed_rate_clk);
|
|
}
|
|
|
|
/* operation functions for pll clocks */
|
|
static const struct samsung_pll_rate_table *samsung_get_pll_settings(
|
|
struct samsung_composite_pll *pll, unsigned long rate)
|
|
{
|
|
const struct samsung_pll_rate_table *rate_table = pll->rate_table;
|
|
int i;
|
|
|
|
for (i = 0; i < pll->rate_count; i++) {
|
|
if (rate == rate_table[i].rate)
|
|
return &rate_table[i];
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static long samsung_pll_round_rate(struct clk_hw *hw,
|
|
unsigned long drate, unsigned long *prate)
|
|
{
|
|
struct samsung_composite_pll *pll = to_comp_pll(hw);
|
|
const struct samsung_pll_rate_table *rate_table = pll->rate_table;
|
|
int i;
|
|
|
|
/* Assumming rate_table is in descending order */
|
|
for (i = 0; i < pll->rate_count; i++) {
|
|
if (drate >= rate_table[i].rate)
|
|
return rate_table[i].rate;
|
|
}
|
|
|
|
/* return minimum supported value */
|
|
return rate_table[i - 1].rate;
|
|
}
|
|
|
|
static int samsung_composite_pll_is_enabled(struct clk_hw *hw)
|
|
{
|
|
struct samsung_composite_pll *pll = to_comp_pll(hw);
|
|
int set = pll->pll_flag & PLL_BYPASS ? 0 : 1;
|
|
unsigned int reg;
|
|
|
|
reg = readl(pll->enable_reg);
|
|
|
|
return (((reg >> pll->enable_bit) & 1) == set) ? 1 : 0;
|
|
}
|
|
|
|
static int samsung_composite_pll_enable(struct clk_hw *hw)
|
|
{
|
|
struct samsung_composite_pll *pll = to_comp_pll(hw);
|
|
int set = pll->pll_flag & PLL_BYPASS ? 0 : 1;
|
|
unsigned int reg;
|
|
|
|
/* Setting Enable register */
|
|
reg = readl(pll->enable_reg);
|
|
if (set)
|
|
reg |= (1 << pll->enable_bit);
|
|
else
|
|
reg &= ~(1 << pll->enable_bit);
|
|
writel(reg, pll->enable_reg);
|
|
|
|
/* setting CTRL mux register to 1 */
|
|
reg = readl(pll->sel_reg);
|
|
reg |= (1 << pll->sel_bit);
|
|
writel(reg, pll->sel_reg);
|
|
|
|
/* check status for mux setting */
|
|
do {
|
|
cpu_relax();
|
|
reg = readl(pll->stat_reg);
|
|
} while (((reg >> pll->stat_bit) & PLL_STAT_MASK) != PLL_STAT_PLL);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void samsung_composite_pll_disable(struct clk_hw *hw)
|
|
{
|
|
struct samsung_composite_pll *pll = to_comp_pll(hw);
|
|
int set = pll->pll_flag & PLL_BYPASS ? 0 : 1;
|
|
unsigned int reg;
|
|
|
|
/* setting CTRL mux register to 0 */
|
|
reg = readl(pll->sel_reg);
|
|
reg &= ~(1 << pll->sel_bit);
|
|
writel(reg, pll->sel_reg);
|
|
|
|
/* check status for mux setting */
|
|
do {
|
|
cpu_relax();
|
|
reg = readl(pll->stat_reg);
|
|
} while (((reg >> pll->stat_bit) & PLL_STAT_MASK) != PLL_STAT_OSC);
|
|
|
|
/* Setting Register */
|
|
reg = readl(pll->enable_reg);
|
|
if (set)
|
|
reg &= ~(1 << pll->enable_bit);
|
|
else
|
|
reg |= (1 << pll->enable_bit);
|
|
|
|
writel(reg, pll->enable_reg);
|
|
}
|
|
|
|
static unsigned long samsung_pll145xx_recalc_rate(struct clk_hw *hw,
|
|
unsigned long parent_rate)
|
|
{
|
|
struct samsung_composite_pll *pll = to_comp_pll(hw);
|
|
u32 mdiv, pdiv, sdiv, pll_con;
|
|
u64 fvco = parent_rate;
|
|
|
|
pll_con = readl(pll->con_reg);
|
|
mdiv = (pll_con >> PLL145XX_MDIV_SHIFT) & PLL145XX_MDIV_MASK;
|
|
pdiv = (pll_con >> PLL145XX_PDIV_SHIFT) & PLL145XX_PDIV_MASK;
|
|
sdiv = (pll_con >> PLL145XX_SDIV_SHIFT) & PLL145XX_SDIV_MASK;
|
|
/* Do calculation */
|
|
fvco *= mdiv;
|
|
do_div(fvco, (pdiv << sdiv));
|
|
|
|
return (unsigned long)fvco;
|
|
}
|
|
|
|
static inline bool samsung_pll145xx_mp_check(u32 mdiv, u32 pdiv, u32 pll_con)
|
|
{
|
|
return ((mdiv != ((pll_con >> PLL145XX_MDIV_SHIFT) & PLL145XX_MDIV_MASK)) ||
|
|
(pdiv != ((pll_con >> PLL145XX_PDIV_SHIFT) & PLL145XX_PDIV_MASK)));
|
|
}
|
|
|
|
static int samsung_pll145xx_set_rate(struct clk_hw *hw, unsigned long drate,
|
|
unsigned long prate)
|
|
{
|
|
struct samsung_composite_pll *pll = to_comp_pll(hw);
|
|
const struct samsung_pll_rate_table *rate;
|
|
u32 pll_con;
|
|
|
|
rate = samsung_get_pll_settings(pll, drate);
|
|
if (!rate) {
|
|
pr_err("%s: Invalid rate : %lu for pll clk %s\n", __func__,
|
|
drate, __clk_get_name(hw->clk));
|
|
return -EINVAL;
|
|
}
|
|
|
|
pll_con = readl(pll->con_reg);
|
|
if (!(samsung_pll145xx_mp_check(rate->mdiv, rate->pdiv, pll_con))) {
|
|
if ((rate->sdiv) == ((pll_con >> PLL145XX_SDIV_SHIFT) & PLL145XX_SDIV_MASK))
|
|
return 0;
|
|
/* In the case of changing S value only */
|
|
pll_con &= ~(PLL145XX_SDIV_MASK << PLL145XX_SDIV_SHIFT);
|
|
pll_con |= rate->sdiv << PLL145XX_SDIV_SHIFT;
|
|
writel(pll_con, pll->con_reg);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Set PLL lock time */
|
|
writel(rate->pdiv * PLL145XX_LOCK_FACTOR, pll->lock_reg);
|
|
/* Change PLL PMS values */
|
|
pll_con &= ~((PLL145XX_MDIV_MASK << PLL145XX_MDIV_SHIFT) |
|
|
(PLL145XX_PDIV_MASK << PLL145XX_PDIV_SHIFT) |
|
|
(PLL145XX_SDIV_MASK << PLL145XX_SDIV_SHIFT));
|
|
pll_con |= (rate->mdiv << PLL145XX_MDIV_SHIFT) |
|
|
(rate->pdiv << PLL145XX_PDIV_SHIFT) |
|
|
(rate->sdiv << PLL145XX_SDIV_SHIFT);
|
|
/* To prevent instable PLL operation, preset ENABLE bit with 0 */
|
|
pll_con &= ~BIT(31);
|
|
writel(pll_con, pll->con_reg);
|
|
|
|
/* Set enable bit */
|
|
pll_con |= BIT(31);
|
|
writel(pll_con, pll->con_reg);
|
|
|
|
do {
|
|
cpu_relax();
|
|
pll_con = readl(pll->con_reg);
|
|
} while (!(pll_con & (PLL145XX_LOCKED_MASK << PLL145XX_LOCKED_SHIFT)));
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static unsigned long samsung_pll1460x_recalc_rate(struct clk_hw *hw,
|
|
unsigned long parent_rate)
|
|
{
|
|
struct samsung_composite_pll *pll = to_comp_pll(hw);
|
|
u32 mdiv, pdiv, sdiv, pll_con0, pll_con1;
|
|
s16 kdiv;
|
|
u64 fvco = parent_rate;
|
|
|
|
pll_con0 = readl(pll->con_reg);
|
|
pll_con1 = readl(pll->con_reg + 4);
|
|
mdiv = (pll_con0 >> PLL1460X_MDIV_SHIFT) & PLL1460X_MDIV_MASK;
|
|
pdiv = (pll_con0 >> PLL1460X_PDIV_SHIFT) & PLL1460X_PDIV_MASK;
|
|
sdiv = (pll_con0 >> PLL1460X_SDIV_SHIFT) & PLL1460X_SDIV_MASK;
|
|
kdiv = (s16)((pll_con1 >> PLL1460X_KDIV_SHIFT) & PLL1460X_KDIV_MASK);
|
|
/* Do calculation */
|
|
fvco *= (mdiv << 16) + kdiv;
|
|
do_div(fvco, (pdiv << sdiv));
|
|
fvco >>= 16;
|
|
|
|
return (unsigned long)fvco;
|
|
}
|
|
|
|
static inline bool samsung_pll1460x_mpk_check(u32 mdiv, u32 pdiv, u32 kdiv, u32 pll_con0, u32 pll_con1)
|
|
{
|
|
return ((mdiv != ((pll_con0 >> PLL1460X_MDIV_SHIFT) & PLL1460X_MDIV_MASK)) ||
|
|
(pdiv != ((pll_con0 >> PLL1460X_PDIV_SHIFT) & PLL1460X_PDIV_MASK)) ||
|
|
(kdiv != ((pll_con1 >> PLL1460X_KDIV_SHIFT) & PLL1460X_KDIV_MASK)));
|
|
}
|
|
|
|
static int samsung_pll1460x_set_rate(struct clk_hw *hw, unsigned long drate,
|
|
unsigned long parent_rate)
|
|
{
|
|
struct samsung_composite_pll *pll = to_comp_pll(hw);
|
|
u32 pll_con0, pll_con1;
|
|
const struct samsung_pll_rate_table *rate;
|
|
|
|
rate = samsung_get_pll_settings(pll, drate);
|
|
if (!rate) {
|
|
pr_err("%s: Invalid rate : %lu for pll clk %s\n", __func__,
|
|
drate, __clk_get_name(hw->clk));
|
|
return -EINVAL;
|
|
}
|
|
|
|
pll_con0 = readl(pll->con_reg);
|
|
pll_con1 = readl(pll->con_reg + 4);
|
|
if (!(samsung_pll1460x_mpk_check(rate->mdiv, rate->pdiv, rate->kdiv, pll_con0, pll_con1))) {
|
|
if ((rate->sdiv) == ((pll_con0 >> PLL1460X_SDIV_SHIFT) & PLL1460X_SDIV_MASK))
|
|
return 0;
|
|
/* In the case of changing S value only */
|
|
pll_con0 &= ~(PLL1460X_SDIV_MASK << PLL1460X_SDIV_SHIFT);
|
|
pll_con0 |= (rate->sdiv << PLL1460X_SDIV_SHIFT);
|
|
writel(pll_con0, pll->con_reg);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Set PLL lock time */
|
|
writel(rate->pdiv * PLL1460X_LOCK_FACTOR, pll->lock_reg);
|
|
|
|
pll_con1 &= ~(PLL1460X_KDIV_MASK << PLL1460X_KDIV_SHIFT);
|
|
pll_con1 |= (rate->kdiv << PLL1460X_KDIV_SHIFT);
|
|
writel(pll_con1, pll->con_reg + 4);
|
|
|
|
pll_con0 &= ~((PLL1460X_MDIV_MASK << PLL1460X_MDIV_SHIFT) |
|
|
(PLL1460X_PDIV_MASK << PLL1460X_PDIV_SHIFT) |
|
|
(PLL1460X_SDIV_MASK << PLL1460X_SDIV_SHIFT));
|
|
pll_con0 |= (rate->mdiv << PLL1460X_MDIV_SHIFT) |
|
|
(rate->pdiv << PLL1460X_PDIV_SHIFT) |
|
|
(rate->sdiv << PLL1460X_SDIV_SHIFT);
|
|
/* To prevent instable PLL operation, preset ENABLE bit with 0 */
|
|
pll_con0 &= ~BIT(31);
|
|
writel(pll_con0, pll->con_reg);
|
|
|
|
/* Set enable bit */
|
|
pll_con0 |= BIT(31);
|
|
writel(pll_con0, pll->con_reg);
|
|
|
|
/* Wait lock time */
|
|
do {
|
|
cpu_relax();
|
|
pll_con0 = readl(pll->con_reg);
|
|
} while (!(pll_con0 & (PLL1460X_LOCKED_MASK << PLL1460X_LOCKED_SHIFT)));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct clk_ops samsung_pll145xx_clk_ops = {
|
|
.recalc_rate = samsung_pll145xx_recalc_rate,
|
|
.set_rate = samsung_pll145xx_set_rate,
|
|
.round_rate = samsung_pll_round_rate,
|
|
.enable = samsung_composite_pll_enable,
|
|
.disable = samsung_composite_pll_disable,
|
|
.is_enabled = samsung_composite_pll_is_enabled,
|
|
};
|
|
|
|
static const struct clk_ops samsung_pll1460x_clk_ops = {
|
|
.recalc_rate = samsung_pll1460x_recalc_rate,
|
|
.set_rate = samsung_pll1460x_set_rate,
|
|
.round_rate = samsung_pll_round_rate,
|
|
.enable = samsung_composite_pll_enable,
|
|
.disable = samsung_composite_pll_disable,
|
|
.is_enabled = samsung_composite_pll_is_enabled,
|
|
};
|
|
|
|
static int samsung_composite_pll_enable_onchange(struct clk_hw *hw)
|
|
{
|
|
struct samsung_composite_pll *pll = to_comp_pll(hw);
|
|
int set = pll->pll_flag & PLL_BYPASS ? 0 : 1;
|
|
unsigned int reg;
|
|
|
|
/* Setting Enable register */
|
|
reg = readl(pll->enable_reg);
|
|
if (set)
|
|
reg |= (1 << pll->enable_bit);
|
|
else
|
|
reg &= ~(1 << pll->enable_bit);
|
|
writel(reg, pll->enable_reg);
|
|
|
|
/* setting CTRL mux register to 1 */
|
|
reg = readl(pll->sel_reg);
|
|
reg |= (1 << pll->sel_bit);
|
|
writel(reg, pll->sel_reg);
|
|
|
|
/* check status for mux setting */
|
|
do {
|
|
cpu_relax();
|
|
reg = readl(pll->stat_reg);
|
|
} while (((reg >> pll->stat_bit) & PLL_STAT_MASK) == PLL_STAT_CHANGE);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void samsung_composite_pll_disable_onchange(struct clk_hw *hw)
|
|
{
|
|
struct samsung_composite_pll *pll = to_comp_pll(hw);
|
|
int set = pll->pll_flag & PLL_BYPASS ? 0 : 1;
|
|
unsigned int reg;
|
|
|
|
/* setting CTRL mux register to 0 */
|
|
reg = readl(pll->sel_reg);
|
|
reg &= ~(1 << pll->sel_bit);
|
|
writel(reg, pll->sel_reg);
|
|
|
|
/* check status for mux setting */
|
|
do {
|
|
cpu_relax();
|
|
reg = readl(pll->stat_reg);
|
|
} while (((reg >> pll->stat_bit) & PLL_STAT_MASK) == PLL_STAT_CHANGE);
|
|
|
|
/* Setting Register */
|
|
reg = readl(pll->enable_reg);
|
|
if (set)
|
|
reg &= ~(1 << pll->enable_bit);
|
|
else
|
|
reg |= (1 << pll->enable_bit);
|
|
|
|
writel(reg, pll->enable_reg);
|
|
}
|
|
|
|
static unsigned long samsung_pll255xx_recalc_rate(struct clk_hw *hw,
|
|
unsigned long parent_rate)
|
|
{
|
|
struct samsung_composite_pll *pll = to_comp_pll(hw);
|
|
u32 mdiv, pdiv, sdiv, pll_con;
|
|
u64 fvco = parent_rate;
|
|
|
|
pll_con = readl(pll->con_reg);
|
|
mdiv = (pll_con >> PLL255XX_MDIV_SHIFT) & PLL255XX_MDIV_MASK;
|
|
pdiv = (pll_con >> PLL255XX_PDIV_SHIFT) & PLL255XX_PDIV_MASK;
|
|
sdiv = (pll_con >> PLL255XX_SDIV_SHIFT) & PLL255XX_SDIV_MASK;
|
|
/* Do calculation */
|
|
fvco *= mdiv;
|
|
do_div(fvco, (pdiv << sdiv));
|
|
|
|
return (unsigned long)fvco;
|
|
}
|
|
|
|
static inline bool samsung_pll255xx_mp_check(u32 mdiv, u32 pdiv, u32 pll_con)
|
|
{
|
|
return ((mdiv != ((pll_con >> PLL255XX_MDIV_SHIFT) & PLL255XX_MDIV_MASK)) ||
|
|
(pdiv != ((pll_con >> PLL255XX_PDIV_SHIFT) & PLL255XX_PDIV_MASK)));
|
|
}
|
|
|
|
static int samsung_pll255xx_set_rate(struct clk_hw *hw, unsigned long drate,
|
|
unsigned long prate)
|
|
{
|
|
struct samsung_composite_pll *pll = to_comp_pll(hw);
|
|
const struct samsung_pll_rate_table *rate;
|
|
u32 pll_con;
|
|
|
|
rate = samsung_get_pll_settings(pll, drate);
|
|
if (!rate) {
|
|
pr_err("%s: Invalid rate : %lu for pll clk %s\n", __func__,
|
|
drate, __clk_get_name(hw->clk));
|
|
return -EINVAL;
|
|
}
|
|
|
|
pll_con = readl(pll->con_reg);
|
|
if (!(samsung_pll255xx_mp_check(rate->mdiv, rate->pdiv, pll_con))) {
|
|
if ((rate->sdiv) == ((pll_con >> PLL255XX_SDIV_SHIFT) & PLL255XX_SDIV_MASK))
|
|
return 0;
|
|
/* In the case of changing S value only */
|
|
pll_con &= ~(PLL255XX_SDIV_MASK << PLL255XX_SDIV_SHIFT);
|
|
pll_con |= rate->sdiv << PLL255XX_SDIV_SHIFT;
|
|
writel(pll_con, pll->con_reg);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Set PLL lock time */
|
|
writel(rate->pdiv * PLL255XX_LOCK_FACTOR, pll->lock_reg);
|
|
/* Change PLL PMS values */
|
|
pll_con &= ~((PLL255XX_MDIV_MASK << PLL255XX_MDIV_SHIFT) |
|
|
(PLL255XX_PDIV_MASK << PLL255XX_PDIV_SHIFT) |
|
|
(PLL255XX_SDIV_MASK << PLL255XX_SDIV_SHIFT));
|
|
pll_con |= (rate->mdiv << PLL255XX_MDIV_SHIFT) |
|
|
(rate->pdiv << PLL255XX_PDIV_SHIFT) |
|
|
(rate->sdiv << PLL255XX_SDIV_SHIFT);
|
|
|
|
/* To prevent unstable PLL operation, preset enable bit with 0 */
|
|
pll_con &= ~BIT(31);
|
|
writel(pll_con, pll->con_reg);
|
|
|
|
/* Set enable bit */
|
|
pll_con |= BIT(31);
|
|
writel(pll_con, pll->con_reg);
|
|
|
|
do {
|
|
cpu_relax();
|
|
pll_con = readl(pll->con_reg);
|
|
} while (!(pll_con & (PLL255XX_LOCKED_MASK << PLL255XX_LOCKED_SHIFT)));
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* register function for pll clocks */
|
|
static const struct clk_ops samsung_pll255xx_clk_ops = {
|
|
.recalc_rate = samsung_pll255xx_recalc_rate,
|
|
.set_rate = samsung_pll255xx_set_rate,
|
|
.round_rate = samsung_pll_round_rate,
|
|
.enable = samsung_composite_pll_enable_onchange,
|
|
.disable = samsung_composite_pll_disable_onchange,
|
|
.is_enabled = samsung_composite_pll_is_enabled,
|
|
};
|
|
|
|
static unsigned long samsung_pll2650x_recalc_rate(struct clk_hw *hw,
|
|
unsigned long parent_rate)
|
|
{
|
|
struct samsung_composite_pll *pll = to_comp_pll(hw);
|
|
u32 mdiv, pdiv, sdiv, pll_con0, pll_con1;
|
|
s16 kdiv;
|
|
u64 fvco = parent_rate;
|
|
|
|
pll_con0 = readl(pll->con_reg);
|
|
pll_con1 = readl(pll->con_reg + 4);
|
|
mdiv = (pll_con0 >> PLL2650X_MDIV_SHIFT) & PLL2650X_MDIV_MASK;
|
|
pdiv = (pll_con0 >> PLL2650X_PDIV_SHIFT) & PLL2650X_PDIV_MASK;
|
|
sdiv = (pll_con0 >> PLL2650X_SDIV_SHIFT) & PLL2650X_SDIV_MASK;
|
|
kdiv = (s16)((pll_con1 >> PLL2650X_KDIV_SHIFT) & PLL2650X_KDIV_MASK);
|
|
/* Do calculation */
|
|
fvco *= (mdiv << 16) + kdiv;
|
|
do_div(fvco, (pdiv << sdiv));
|
|
fvco >>= 16;
|
|
|
|
return (unsigned long)fvco;
|
|
}
|
|
|
|
static inline bool samsung_pll2650x_mpk_check(u32 mdiv, u32 pdiv, u32 kdiv, u32 pll_con0, u32 pll_con1)
|
|
{
|
|
return ((mdiv != ((pll_con0 >> PLL2650X_MDIV_SHIFT) & PLL2650X_MDIV_MASK)) ||
|
|
(pdiv != ((pll_con0 >> PLL2650X_PDIV_SHIFT) & PLL2650X_PDIV_MASK)) ||
|
|
(kdiv != ((pll_con1 >> PLL2650X_KDIV_SHIFT) & PLL2650X_KDIV_MASK)));
|
|
}
|
|
|
|
static int samsung_pll2650x_set_rate(struct clk_hw *hw, unsigned long drate,
|
|
unsigned long parent_rate)
|
|
{
|
|
struct samsung_composite_pll *pll = to_comp_pll(hw);
|
|
u32 pll_con0, pll_con1;
|
|
const struct samsung_pll_rate_table *rate;
|
|
|
|
rate = samsung_get_pll_settings(pll, drate);
|
|
if (!rate) {
|
|
pr_err("%s: Invalid rate : %lu for pll clk %s\n", __func__,
|
|
drate, __clk_get_name(hw->clk));
|
|
return -EINVAL;
|
|
}
|
|
|
|
pll_con0 = readl(pll->con_reg);
|
|
pll_con1 = readl(pll->con_reg + 4);
|
|
if (!(samsung_pll2650x_mpk_check(rate->mdiv, rate->pdiv, rate->kdiv, pll_con0, pll_con1))) {
|
|
if ((rate->sdiv) == ((pll_con0 >> PLL2650X_SDIV_SHIFT) & PLL2650X_SDIV_MASK))
|
|
return 0;
|
|
/* In the case of changing S value only */
|
|
pll_con0 &= ~(PLL2650X_SDIV_MASK << PLL2650X_SDIV_SHIFT);
|
|
pll_con0 |= (rate->sdiv << PLL2650X_SDIV_SHIFT);
|
|
writel(pll_con0, pll->con_reg);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Set PLL lock time */
|
|
writel(rate->pdiv * PLL2650X_LOCK_FACTOR, pll->lock_reg);
|
|
|
|
pll_con1 &= ~(PLL2650X_KDIV_MASK << PLL2650X_KDIV_SHIFT);
|
|
pll_con1 |= (rate->kdiv << PLL2650X_KDIV_SHIFT);
|
|
writel(pll_con1, pll->con_reg + 4);
|
|
|
|
pll_con0 &= ~((PLL2650X_MDIV_MASK << PLL2650X_MDIV_SHIFT) |
|
|
(PLL2650X_PDIV_MASK << PLL2650X_PDIV_SHIFT) |
|
|
(PLL2650X_SDIV_MASK << PLL2650X_SDIV_SHIFT));
|
|
pll_con0 |= (rate->mdiv << PLL2650X_MDIV_SHIFT) |
|
|
(rate->pdiv << PLL2650X_PDIV_SHIFT) |
|
|
(rate->sdiv << PLL2650X_SDIV_SHIFT);
|
|
|
|
/* To prevent unstable PLL operation, preset enable bit with 0 */
|
|
pll_con0 &= ~BIT(31);
|
|
writel(pll_con0, pll->con_reg);
|
|
|
|
/* Set enable bit */
|
|
pll_con0 |= BIT(31);
|
|
writel(pll_con0, pll->con_reg);
|
|
|
|
/*
|
|
* Wait lock time
|
|
* unit address translation : us to ms for mdelay
|
|
*/
|
|
mdelay(rate->pdiv * PLL2650X_LOCK_FACTOR / 1000);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct clk_ops samsung_pll2650x_clk_ops = {
|
|
.recalc_rate = samsung_pll2650x_recalc_rate,
|
|
.set_rate = samsung_pll2650x_set_rate,
|
|
.round_rate = samsung_pll_round_rate,
|
|
.enable = samsung_composite_pll_enable_onchange,
|
|
.disable = samsung_composite_pll_disable_onchange,
|
|
.is_enabled = samsung_composite_pll_is_enabled,
|
|
};
|
|
|
|
/* register function for pll clocks */
|
|
static void _samsung_register_comp_pll(struct samsung_clk_provider *ctx,
|
|
struct samsung_composite_pll *list)
|
|
{
|
|
struct clk *clk;
|
|
static const char *pname[1] = {"fin_pll"};
|
|
int len;
|
|
unsigned int ret = 0;
|
|
|
|
if (list->rate_table) {
|
|
/* find count of rates in rate_table */
|
|
for (len = 0; list->rate_table[len].rate != 0; )
|
|
len++;
|
|
list->rate_count = len; }
|
|
else
|
|
list->rate_count = 0;
|
|
|
|
if (list->type == pll_1450x)
|
|
clk = clk_register_composite(NULL, list->name, pname, 1,
|
|
NULL, NULL,
|
|
&list->hw, &samsung_pll145xx_clk_ops,
|
|
&list->hw, &samsung_pll145xx_clk_ops, list->flag);
|
|
else if (list->type == pll_1451x || list->type == pll_1452x)
|
|
clk = clk_register_composite(NULL, list->name, pname, 1,
|
|
NULL, NULL,
|
|
&list->hw, &samsung_pll145xx_clk_ops,
|
|
&list->hw, &samsung_pll145xx_clk_ops, list->flag);
|
|
else if (list->type == pll_1460x)
|
|
clk = clk_register_composite(NULL, list->name, pname, 1,
|
|
NULL, NULL,
|
|
&list->hw, &samsung_pll1460x_clk_ops,
|
|
&list->hw, &samsung_pll1460x_clk_ops, list->flag);
|
|
else if (list->type == pll_2551x || list->type == pll_2555x)
|
|
clk = clk_register_composite(NULL, list->name, pname, 1,
|
|
NULL, NULL,
|
|
&list->hw, &samsung_pll255xx_clk_ops,
|
|
&list->hw, &samsung_pll255xx_clk_ops, list->flag);
|
|
else if (list->type == pll_2650x)
|
|
clk = clk_register_composite(NULL, list->name, pname, 1,
|
|
NULL, NULL,
|
|
&list->hw, &samsung_pll2650x_clk_ops,
|
|
&list->hw, &samsung_pll2650x_clk_ops, list->flag);
|
|
else {
|
|
pr_err("%s: invalid pll type %d\n", __func__, list->type);
|
|
return;
|
|
}
|
|
|
|
if (IS_ERR(clk)) {
|
|
pr_err("%s: failed to register pll clock %s\n",
|
|
__func__, list->name);
|
|
return;
|
|
}
|
|
|
|
samsung_clk_add_lookup(ctx, clk, list->id);
|
|
|
|
/* register a clock lookup only if a clock alias is specified */
|
|
if (list->alias) {
|
|
ret = clk_register_clkdev(clk, list->alias, NULL);
|
|
if (ret)
|
|
pr_err("%s: failed to register lookup %s\n",
|
|
__func__, list->alias);
|
|
}
|
|
}
|
|
|
|
void samsung_register_comp_pll(struct samsung_clk_provider *ctx,
|
|
struct samsung_composite_pll *list, unsigned int nr_pll)
|
|
{
|
|
int cnt;
|
|
|
|
for (cnt = 0; cnt < nr_pll; cnt++)
|
|
_samsung_register_comp_pll(ctx, &list[cnt]);
|
|
}
|
|
|
|
/* operation functions for mux clocks */
|
|
static u8 samsung_mux_get_parent(struct clk_hw *hw)
|
|
{
|
|
struct samsung_composite_mux *mux = to_comp_mux(hw);
|
|
u32 val;
|
|
|
|
val = readl(mux->sel_reg) >> mux->sel_bit;
|
|
val &= (BIT(mux->sel_width) - 1);
|
|
|
|
return (u8)val;
|
|
}
|
|
|
|
static int samsung_mux_set_parent(struct clk_hw *hw, u8 index)
|
|
{
|
|
struct samsung_composite_mux *mux = to_comp_mux(hw);
|
|
u32 val;
|
|
unsigned long flags = 0;
|
|
unsigned int timeout = 1000;
|
|
|
|
if (mux->lock)
|
|
spin_lock_irqsave(mux->lock, flags);
|
|
|
|
val = readl(mux->sel_reg);
|
|
val &= ~((BIT(mux->sel_width) - 1) << mux->sel_bit);
|
|
val |= index << mux->sel_bit;
|
|
writel(val, mux->sel_reg);
|
|
|
|
if (mux->stat_reg)
|
|
do {
|
|
--timeout;
|
|
if (!timeout) {
|
|
pr_err("%s: failed to set parent %s.\n",
|
|
__func__, clk_hw_get_name(hw));
|
|
pr_err("MUX_REG: %08x, MUX_STAT_REG: %08x\n",
|
|
readl(mux->sel_reg), readl(mux->stat_reg));
|
|
if (mux->lock)
|
|
spin_unlock_irqrestore(mux->lock, flags);
|
|
return -ETIMEDOUT;
|
|
}
|
|
val = readl(mux->stat_reg);
|
|
val &= ((BIT(mux->stat_width) - 1) << mux->stat_bit);
|
|
} while (val != (index << mux->stat_bit));
|
|
|
|
if (mux->lock)
|
|
spin_unlock_irqrestore(mux->lock, flags);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct clk_ops samsung_composite_mux_ops = {
|
|
.get_parent = samsung_mux_get_parent,
|
|
.set_parent = samsung_mux_set_parent,
|
|
};
|
|
|
|
/* operation functions for mux clocks checking status with "on changing" */
|
|
|
|
static int samsung_mux_set_parent_onchange(struct clk_hw *hw, u8 index)
|
|
{
|
|
struct samsung_composite_mux *mux = to_comp_mux(hw);
|
|
u32 val;
|
|
unsigned long flags = 0;
|
|
unsigned int timeout = 1000;
|
|
|
|
if (mux->lock)
|
|
spin_lock_irqsave(mux->lock, flags);
|
|
|
|
val = readl(mux->sel_reg);
|
|
val &= ~((BIT(mux->sel_width) - 1) << mux->sel_bit);
|
|
val |= index << mux->sel_bit;
|
|
writel(val, mux->sel_reg);
|
|
|
|
if (mux->stat_reg)
|
|
do {
|
|
--timeout;
|
|
if (!timeout) {
|
|
pr_err("%s: failed to set parent %s.\n",
|
|
__func__, clk_hw_get_name(hw));
|
|
pr_err("MUX_REG: %08x, MUX_STAT_REG: %08x\n",
|
|
readl(mux->sel_reg), readl(mux->stat_reg));
|
|
if (mux->lock)
|
|
spin_unlock_irqrestore(mux->lock, flags);
|
|
return -ETIMEDOUT;
|
|
}
|
|
val = readl(mux->stat_reg);
|
|
val &= ((BIT(mux->stat_width) - 1) << mux->stat_bit);
|
|
} while (val == PLL_STAT_CHANGE);
|
|
|
|
if (mux->lock)
|
|
spin_unlock_irqrestore(mux->lock, flags);
|
|
|
|
return 0;
|
|
}
|
|
static const struct clk_ops samsung_composite_mux_ops_onchange = {
|
|
.get_parent = samsung_mux_get_parent,
|
|
.set_parent = samsung_mux_set_parent_onchange,
|
|
};
|
|
|
|
/* register function for mux clock */
|
|
static void _samsung_register_comp_mux(struct samsung_clk_provider *ctx,
|
|
struct samsung_composite_mux *list)
|
|
{
|
|
struct clk *clk;
|
|
unsigned int ret = 0;
|
|
|
|
list->lock = &lock;
|
|
|
|
if (!(list->flag & CLK_ON_CHANGING))
|
|
clk = clk_register_composite(NULL, list->name, list->parents, list->num_parents,
|
|
&list->hw, &samsung_composite_mux_ops,
|
|
NULL, NULL,
|
|
NULL, NULL, list->flag);
|
|
else
|
|
clk = clk_register_composite(NULL, list->name, list->parents, list->num_parents,
|
|
&list->hw, &samsung_composite_mux_ops_onchange,
|
|
NULL, NULL,
|
|
NULL, NULL, list->flag);
|
|
|
|
if (IS_ERR(clk)) {
|
|
pr_err("%s: failed to register mux clock %s\n",
|
|
__func__, list->name);
|
|
return;
|
|
}
|
|
|
|
samsung_clk_add_lookup(ctx, clk, list->id);
|
|
|
|
/* register a clock lookup only if a clock alias is specified */
|
|
if (list->alias) {
|
|
ret = clk_register_clkdev(clk, list->alias, NULL);
|
|
if (ret)
|
|
pr_err("%s: failed to register lookup %s\n",
|
|
__func__, list->alias);
|
|
}
|
|
}
|
|
|
|
void samsung_register_comp_mux(struct samsung_clk_provider *ctx,
|
|
struct samsung_composite_mux *list, unsigned int nr_mux)
|
|
{
|
|
int cnt;
|
|
|
|
for (cnt = 0; cnt < nr_mux; cnt++)
|
|
_samsung_register_comp_mux(ctx, &list[cnt]);
|
|
}
|
|
|
|
/* operation functions for divider clocks */
|
|
static unsigned long samsung_divider_recalc_rate(struct clk_hw *hw,
|
|
unsigned long parent_rate)
|
|
{
|
|
struct samsung_composite_divider *divider = to_comp_divider(hw);
|
|
unsigned int val;
|
|
|
|
val = readl(divider->rate_reg) >> divider->rate_bit;
|
|
val &= (1 << divider->rate_width) - 1;
|
|
val += 1;
|
|
if (!val)
|
|
return parent_rate;
|
|
|
|
return parent_rate / val;
|
|
}
|
|
|
|
static int samsung_divider_bestdiv(struct clk_hw *hw, unsigned long rate,
|
|
unsigned long *best_parent_rate)
|
|
{
|
|
struct samsung_composite_divider *divider = to_comp_divider(hw);
|
|
int i, bestdiv = 0;
|
|
unsigned long parent_rate, maxdiv, now, best = 0;
|
|
unsigned long parent_rate_saved = *best_parent_rate;
|
|
|
|
if (!rate)
|
|
rate = 1;
|
|
|
|
maxdiv = ((unsigned long)(1UL << (divider->rate_width)) - 1UL) + 1UL;
|
|
|
|
if (!(clk_hw_get_flags(hw) & CLK_SET_RATE_PARENT)) {
|
|
parent_rate = *best_parent_rate;
|
|
bestdiv = (parent_rate + rate - 1) / rate;
|
|
bestdiv = bestdiv == 0 ? 1 : bestdiv;
|
|
bestdiv = bestdiv > maxdiv ? maxdiv : bestdiv;
|
|
return bestdiv;
|
|
}
|
|
|
|
maxdiv = min(ULONG_MAX / rate, maxdiv);
|
|
|
|
for (i = 1; i <= maxdiv; i++) {
|
|
if (rate * i == parent_rate_saved) {
|
|
/*
|
|
* It's the most ideal case if the requested rate can be
|
|
* divided from parent clock without needing to change
|
|
* parent rate, so return the divider immediately.
|
|
*/
|
|
*best_parent_rate = parent_rate_saved;
|
|
return i;
|
|
}
|
|
parent_rate = clk_round_rate(clk_get_parent(hw->clk),
|
|
((rate * i) + i - 1));
|
|
now = parent_rate / i;
|
|
if (now <= rate && now > best) {
|
|
bestdiv = i;
|
|
best = now;
|
|
*best_parent_rate = parent_rate;
|
|
}
|
|
}
|
|
|
|
if (!bestdiv) {
|
|
bestdiv = ((1 << (divider->rate_width)) - 1) + 1;
|
|
*best_parent_rate = clk_round_rate(clk_get_parent(hw->clk), 1);
|
|
}
|
|
|
|
return bestdiv;
|
|
}
|
|
|
|
static long samsung_divider_round_rate(struct clk_hw *hw, unsigned long rate,
|
|
unsigned long *prate)
|
|
{
|
|
int div = 1;
|
|
|
|
div = samsung_divider_bestdiv(hw, rate, prate);
|
|
if (div == 0) {
|
|
pr_err("%s: divider value should not be %d\n", __func__, div);
|
|
div = 1;
|
|
}
|
|
|
|
return *prate / div;
|
|
}
|
|
|
|
static int samsung_divider_set_rate(struct clk_hw *hw, unsigned long rate,
|
|
unsigned long parent_rate)
|
|
{
|
|
struct samsung_composite_divider *divider = to_comp_divider(hw);
|
|
unsigned int div;
|
|
u32 val;
|
|
unsigned long flags = 0;
|
|
unsigned int timeout = 1000;
|
|
|
|
div = (parent_rate / rate) - 1;
|
|
|
|
if (div > ((1 << divider->rate_width) - 1))
|
|
div = (1 << divider->rate_width) - 1;
|
|
|
|
if (divider->lock)
|
|
spin_lock_irqsave(divider->lock, flags);
|
|
|
|
val = readl(divider->rate_reg);
|
|
val &= ~(((1 << divider->rate_width) - 1) << divider->rate_bit);
|
|
val |= div << divider->rate_bit;
|
|
writel(val, divider->rate_reg);
|
|
|
|
if (divider->stat_reg)
|
|
do {
|
|
--timeout;
|
|
if (!timeout) {
|
|
pr_err("%s: faild to set rate %s.\n",
|
|
__func__, clk_hw_get_name(hw));
|
|
pr_err("DIV_REG: %08x, MUX_STAT_REG: %08x\n",
|
|
readl(divider->rate_reg), readl(divider->stat_reg));
|
|
if (divider->lock)
|
|
spin_unlock_irqrestore(divider->lock, flags);
|
|
return -ETIMEDOUT;
|
|
}
|
|
val = readl(divider->stat_reg);
|
|
val &= BIT(divider->stat_width - 1) << divider->stat_bit;
|
|
} while (val);
|
|
|
|
if (divider->lock)
|
|
spin_unlock_irqrestore(divider->lock, flags);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct clk_ops samsung_composite_divider_ops = {
|
|
.recalc_rate = samsung_divider_recalc_rate,
|
|
.round_rate = samsung_divider_round_rate,
|
|
.set_rate = samsung_divider_set_rate,
|
|
};
|
|
|
|
/* register function for divider clocks */
|
|
static void _samsung_register_comp_divider(struct samsung_clk_provider *ctx,
|
|
struct samsung_composite_divider *list)
|
|
{
|
|
struct clk *clk;
|
|
unsigned int ret = 0;
|
|
|
|
list->lock = &lock;
|
|
|
|
clk = clk_register_composite(NULL, list->name, &list->parent_name, 1,
|
|
NULL, NULL,
|
|
&list->hw, &samsung_composite_divider_ops,
|
|
NULL, NULL, list->flag);
|
|
|
|
if (IS_ERR(clk)) {
|
|
pr_err("%s: failed to register mux clock %s\n",
|
|
__func__, list->name);
|
|
return;
|
|
}
|
|
|
|
samsung_clk_add_lookup(ctx, clk, list->id);
|
|
|
|
/* register a clock lookup only if a clock alias is specified */
|
|
if (list->alias) {
|
|
ret = clk_register_clkdev(clk, list->alias, NULL);
|
|
if (ret)
|
|
pr_err("%s: failed to register lookup %s\n",
|
|
__func__, list->alias);
|
|
}
|
|
}
|
|
|
|
void samsung_register_comp_divider(struct samsung_clk_provider *ctx,
|
|
struct samsung_composite_divider *list, unsigned int nr_div)
|
|
{
|
|
int cnt;
|
|
|
|
for (cnt = 0; cnt < nr_div; cnt++)
|
|
_samsung_register_comp_divider(ctx, &list[cnt]);
|
|
}
|
|
|
|
struct dummy_gate_clk {
|
|
unsigned long offset;
|
|
u8 bit_idx;
|
|
struct clk *clk;
|
|
};
|
|
|
|
static struct dummy_gate_clk **gate_clk_list;
|
|
static unsigned int gate_clk_nr;
|
|
|
|
int samsung_add_clk_gate_list(struct clk *clk, unsigned long offset, u8 bit_idx, const char *name)
|
|
{
|
|
struct dummy_gate_clk *tmp_clk;
|
|
|
|
if (!clk || !offset)
|
|
return -EINVAL;
|
|
|
|
tmp_clk = kzalloc(sizeof(struct dummy_gate_clk), GFP_KERNEL);
|
|
if (!tmp_clk) {
|
|
pr_err("%s: fail to alloc for gate_clk\n", __func__);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
tmp_clk->offset = offset;
|
|
tmp_clk->bit_idx = bit_idx;
|
|
tmp_clk->clk = clk;
|
|
|
|
gate_clk_list[gate_clk_nr] = tmp_clk;
|
|
|
|
gate_clk_nr++;
|
|
|
|
return 0;
|
|
}
|
|
|
|
struct clk *samsung_clk_get_by_reg(unsigned long offset, u8 bit_idx)
|
|
{
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < gate_clk_nr; i++) {
|
|
if (gate_clk_list[i]->offset == offset) {
|
|
if (gate_clk_list[i]->bit_idx == bit_idx)
|
|
return gate_clk_list[i]->clk;
|
|
}
|
|
}
|
|
|
|
pr_err("%s: Fail to get clk by register offset\n", __func__);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* existing register function for gate clocks */
|
|
static struct clk * __init _samsung_register_gate(
|
|
struct samsung_clk_provider *ctx, struct samsung_gate *list)
|
|
{
|
|
struct clk *clk;
|
|
unsigned int ret = 0;
|
|
|
|
clk = clk_register_gate(NULL, list->name, list->parent_name,
|
|
list->flag, list->reg, list->bit,
|
|
list->flag, &lock);
|
|
|
|
if (IS_ERR(clk)) {
|
|
pr_err("%s: failed to register clock %s\n", __func__,
|
|
list->name);
|
|
return 0;
|
|
}
|
|
|
|
samsung_clk_add_lookup(ctx, clk, list->id);
|
|
|
|
if (list->alias) {
|
|
ret = clk_register_clkdev(clk, list->alias, NULL);
|
|
if (ret)
|
|
pr_err("%s: failed to register lookup %s\n",
|
|
__func__, list->alias);
|
|
}
|
|
|
|
return clk;
|
|
}
|
|
|
|
void __init samsung_register_gate(struct samsung_clk_provider *ctx,
|
|
struct samsung_gate *list, unsigned int nr_gate)
|
|
{
|
|
int cnt;
|
|
struct clk *clk;
|
|
bool gate_list_fail = false;
|
|
unsigned int gate_enable_nr = 0;
|
|
struct clk **gate_enable_list;
|
|
|
|
gate_clk_list = kzalloc(sizeof(struct dummy_gate_clk *) * nr_gate, GFP_KERNEL);
|
|
if (!gate_clk_list) {
|
|
pr_err("%s: can not alloc for gate clock list\n", __func__);
|
|
gate_list_fail = true;
|
|
}
|
|
|
|
gate_enable_list = kzalloc(sizeof(struct clk *) * nr_gate, GFP_KERNEL);
|
|
|
|
if (!gate_enable_list) {
|
|
pr_err("%s: can not alloc for enable gate clock list\n", __func__);
|
|
return ;
|
|
}
|
|
|
|
for (cnt = 0; cnt < nr_gate; cnt++) {
|
|
clk = _samsung_register_gate(ctx, &list[cnt]);
|
|
|
|
if (((&list[cnt])->flag & CLK_GATE_ENABLE) && gate_enable_list) {
|
|
gate_enable_list[gate_enable_nr] = clk;
|
|
gate_enable_nr++;
|
|
}
|
|
|
|
/* Make list for gate clk to used by samsung_clk_get_by_reg */
|
|
if (!gate_list_fail)
|
|
samsung_add_clk_gate_list(clk, (unsigned long)(list[cnt].reg), list[cnt].bit, list[cnt].name);
|
|
}
|
|
|
|
/*
|
|
* Enable for not controlling gate clocks
|
|
*/
|
|
for (cnt = 0; cnt < gate_enable_nr; cnt++)
|
|
clk_prepare_enable(gate_enable_list[cnt]);
|
|
|
|
if (gate_enable_list)
|
|
kfree(gate_enable_list);
|
|
}
|
|
|
|
/* operation functions for usermux clocks */
|
|
static int samsung_usermux_is_enabled(struct clk_hw *hw)
|
|
{
|
|
struct clk_samsung_usermux *usermux = to_usermux(hw);
|
|
u32 val;
|
|
|
|
val = readl(usermux->sel_reg);
|
|
val &= BIT(usermux->sel_bit);
|
|
|
|
return val ? 1 : 0;
|
|
}
|
|
|
|
static int samsung_usermux_enable(struct clk_hw *hw)
|
|
{
|
|
struct clk_samsung_usermux *usermux = to_usermux(hw);
|
|
u32 val;
|
|
unsigned long flags = 0;
|
|
unsigned int timeout = 1000;
|
|
|
|
if (usermux->lock)
|
|
spin_lock_irqsave(usermux->lock, flags);
|
|
|
|
val = readl(usermux->sel_reg);
|
|
val &= ~(1 << usermux->sel_bit);
|
|
val |= (1 << usermux->sel_bit);
|
|
writel(val, usermux->sel_reg);
|
|
|
|
if (usermux->stat_reg)
|
|
do {
|
|
--timeout;
|
|
if (!timeout) {
|
|
pr_err("%s: failed to enable clock %s.\n",
|
|
__func__, clk_hw_get_name(hw));
|
|
if (usermux->lock)
|
|
spin_unlock_irqrestore(usermux->lock, flags);
|
|
return -ETIMEDOUT;
|
|
}
|
|
val = readl(usermux->stat_reg);
|
|
val &= BIT(2) << usermux->stat_bit;
|
|
} while (val);
|
|
|
|
if (usermux->lock)
|
|
spin_unlock_irqrestore(usermux->lock, flags);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void samsung_usermux_disable(struct clk_hw *hw)
|
|
{
|
|
struct clk_samsung_usermux *usermux = to_usermux(hw);
|
|
u32 val;
|
|
unsigned long flags = 0;
|
|
unsigned int timeout = 1000;
|
|
|
|
if (usermux->lock)
|
|
spin_lock_irqsave(usermux->lock, flags);
|
|
|
|
val = readl(usermux->sel_reg);
|
|
val &= ~(1 << usermux->sel_bit);
|
|
writel(val, usermux->sel_reg);
|
|
|
|
if (usermux->stat_reg)
|
|
do {
|
|
--timeout;
|
|
if (!timeout) {
|
|
pr_err("%s: failed to disable clock %s.\n",
|
|
__func__, clk_hw_get_name(hw));
|
|
if (usermux->lock)
|
|
spin_unlock_irqrestore(usermux->lock, flags);
|
|
return;
|
|
}
|
|
val = readl(usermux->stat_reg);
|
|
val &= BIT(2) << usermux->stat_bit;
|
|
} while (val);
|
|
|
|
if (usermux->lock)
|
|
spin_unlock_irqrestore(usermux->lock, flags);
|
|
}
|
|
|
|
static const struct clk_ops samsung_usermux_ops = {
|
|
.enable = samsung_usermux_enable,
|
|
.disable = samsung_usermux_disable,
|
|
.is_enabled = samsung_usermux_is_enabled,
|
|
};
|
|
|
|
/* register function for usermux clocks */
|
|
static struct clk * __init _samsung_register_comp_usermux(struct samsung_usermux *list)
|
|
{
|
|
struct clk_samsung_usermux *usermux;
|
|
struct clk *clk;
|
|
struct clk_init_data init;
|
|
|
|
usermux = kzalloc(sizeof(struct clk_samsung_usermux), GFP_KERNEL);
|
|
if (!usermux) {
|
|
pr_err("%s: could not allocate usermux clk\n", __func__);
|
|
return ERR_PTR(-ENOMEM);
|
|
}
|
|
|
|
init.name = list->name;
|
|
init.ops = &samsung_usermux_ops;
|
|
init.flags = list->flag | CLK_IS_BASIC;
|
|
init.parent_names = (list->parent_name ? &list->parent_name : NULL);
|
|
init.num_parents = (list->parent_name ? 1 : 0);
|
|
|
|
usermux->sel_reg = list->sel_reg;
|
|
usermux->sel_bit = list->sel_bit;
|
|
usermux->stat_reg = list->stat_reg;
|
|
usermux->stat_bit = list->stat_bit;
|
|
usermux->flag = 0;
|
|
usermux->lock = &lock;
|
|
usermux->hw.init = &init;
|
|
|
|
clk = clk_register(NULL, &usermux->hw);
|
|
|
|
if (IS_ERR(clk))
|
|
kfree(usermux);
|
|
|
|
return clk;
|
|
}
|
|
|
|
void __init samsung_register_usermux(struct samsung_clk_provider *ctx,
|
|
struct samsung_usermux *list, unsigned int nr_usermux)
|
|
{
|
|
struct clk *clk;
|
|
int cnt;
|
|
unsigned int ret = 0;
|
|
|
|
for (cnt = 0; cnt < nr_usermux; cnt++) {
|
|
clk = _samsung_register_comp_usermux(&list[cnt]);
|
|
if (IS_ERR(clk)) {
|
|
pr_err("%s: failed to register clock %s\n", __func__,
|
|
(&list[cnt])->name);
|
|
return;
|
|
}
|
|
|
|
samsung_clk_add_lookup(ctx, clk, (&list[cnt])->id);
|
|
|
|
if ((&list[cnt])->alias) {
|
|
ret = clk_register_clkdev(clk, (&list[cnt])->alias, NULL);
|
|
if (ret)
|
|
pr_err("%s: failed to register lookup %s\n",
|
|
__func__, (&list[cnt])->alias);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Operations for virtual clock used in cal
|
|
* When cal is used to set clocks, following operations will be executed.
|
|
*/
|
|
int cal_vclk_enable(struct clk_hw *hw)
|
|
{
|
|
struct samsung_vclk *vclk = to_vclk(hw);
|
|
unsigned long flags = 0;
|
|
int ret = 0;
|
|
|
|
if ((vclk->flags & VCLK_GATE) && !(vclk->flags & VCLK_QCH_DIS))
|
|
return 0;
|
|
|
|
if (vclk->lock)
|
|
spin_lock_irqsave(vclk->lock, flags);
|
|
|
|
dbg_snapshot_clk(hw, __func__, 1, DSS_FLAG_IN);
|
|
/* Call cal api to enable virtual clock */
|
|
ret = cal_clk_enable(vclk->id);
|
|
if (ret) {
|
|
pr_err("[CAL]%s failed %d %d.\n", __func__, vclk->id, ret);
|
|
dbg_snapshot_clk(hw, __func__, 1, DSS_FLAG_ON);
|
|
if (vclk->lock)
|
|
spin_unlock_irqrestore(vclk->lock, flags);
|
|
return -EAGAIN;
|
|
}
|
|
dbg_snapshot_clk(hw, __func__, 1, DSS_FLAG_OUT);
|
|
|
|
if (vclk->lock)
|
|
spin_unlock_irqrestore(vclk->lock, flags);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void cal_vclk_disable(struct clk_hw *hw)
|
|
{
|
|
struct samsung_vclk *vclk = to_vclk(hw);
|
|
unsigned long flags = 0;
|
|
int ret = 0;
|
|
|
|
if ((vclk->flags & VCLK_GATE) && !(vclk->flags & VCLK_QCH_DIS))
|
|
return ;
|
|
|
|
if (vclk->lock)
|
|
spin_lock_irqsave(vclk->lock, flags);
|
|
|
|
dbg_snapshot_clk(hw, __func__, 0, DSS_FLAG_IN);
|
|
/* Call cal api to disable virtual clock */
|
|
ret = cal_clk_disable(vclk->id);
|
|
if (ret) {
|
|
pr_err("[CAL]%s failed %d %d.\n", __func__, vclk->id, ret);
|
|
dbg_snapshot_clk(hw, __func__, 0, DSS_FLAG_ON);
|
|
if (vclk->lock)
|
|
spin_unlock_irqrestore(vclk->lock, flags);
|
|
return;
|
|
}
|
|
dbg_snapshot_clk(hw, __func__, 0, DSS_FLAG_OUT);
|
|
|
|
if (vclk->lock)
|
|
spin_unlock_irqrestore(vclk->lock, flags);
|
|
}
|
|
|
|
int cal_vclk_is_enabled(struct clk_hw *hw)
|
|
{
|
|
struct samsung_vclk *vclk = to_vclk(hw);
|
|
int ret = 0;
|
|
|
|
/*
|
|
* Call cal api to check whether clock is enabled or not
|
|
* Spinlock is not needed because only read operation will
|
|
* be executed
|
|
*/
|
|
ret = cal_clk_is_enabled(vclk->id);
|
|
|
|
return ret;
|
|
}
|
|
|
|
unsigned long cal_vclk_recalc_rate(struct clk_hw *hw, unsigned long parent_rate)
|
|
{
|
|
struct samsung_vclk *vclk = to_vclk(hw);
|
|
unsigned long ret = 0;
|
|
|
|
dbg_snapshot_clk(hw, __func__, 0, DSS_FLAG_IN);
|
|
/* Call cal api to recalculate rate */
|
|
ret = cal_clk_getrate(vclk->id);
|
|
dbg_snapshot_clk(hw, __func__, ret, DSS_FLAG_OUT);
|
|
|
|
return ret;
|
|
}
|
|
|
|
unsigned long cal_vclk_gate_recalc_rate(struct clk_hw *hw, unsigned long parent_rate)
|
|
{
|
|
struct samsung_vclk *vclk;
|
|
struct clk_hw *parent;
|
|
unsigned long ret = 0;
|
|
|
|
dbg_snapshot_clk(hw, __func__, 0, DSS_FLAG_IN);
|
|
parent = clk_hw_get_parent(hw);
|
|
if (parent) {
|
|
vclk = to_vclk(parent);
|
|
/* call cal api to recalculate rate */
|
|
ret = cal_clk_getrate(vclk->id);
|
|
}
|
|
dbg_snapshot_clk(hw, __func__, ret, DSS_FLAG_OUT);
|
|
|
|
return ret;
|
|
}
|
|
|
|
long cal_vclk_round_rate(struct clk_hw *hw, unsigned long rate,
|
|
unsigned long *prate)
|
|
{
|
|
/* round_rate ops is not needed when using cal */
|
|
return (long)rate;
|
|
}
|
|
|
|
int cal_vclk_set_rate(struct clk_hw *hw, unsigned long rate, unsigned long prate)
|
|
{
|
|
struct samsung_vclk *vclk = to_vclk(hw);
|
|
unsigned long flags = 0;
|
|
int ret = 0;
|
|
|
|
if (vclk->lock)
|
|
spin_lock_irqsave(vclk->lock, flags);
|
|
|
|
dbg_snapshot_clk(hw, __func__, rate, DSS_FLAG_IN);
|
|
/* Call cal api to set rate of clock */
|
|
ret = cal_clk_setrate(vclk->id, rate);
|
|
if (ret) {
|
|
pr_err("[CAL]%s failed %d %lu %d.\n", __func__,
|
|
vclk->id, rate, ret);
|
|
dbg_snapshot_clk(hw, __func__, rate, DSS_FLAG_ON);
|
|
if (vclk->lock)
|
|
spin_unlock_irqrestore(vclk->lock, flags);
|
|
return -EAGAIN;
|
|
}
|
|
dbg_snapshot_clk(hw, __func__, rate, DSS_FLAG_OUT);
|
|
|
|
if (vclk->lock)
|
|
spin_unlock_irqrestore(vclk->lock, flags);
|
|
|
|
return ret;
|
|
}
|
|
|
|
unsigned long cal_vclk_dfs_sw_recalc_rate(struct clk_hw *hw, unsigned long parent_rate)
|
|
{
|
|
struct samsung_vclk *vclk = to_vclk(hw);
|
|
unsigned long ret = 0;
|
|
|
|
/* Call cal api to recalculate rate */
|
|
ret = cal_dfs_cached_get_rate(vclk->id);
|
|
|
|
return ret;
|
|
}
|
|
unsigned long cal_vclk_dfs_recalc_rate(struct clk_hw *hw, unsigned long parent_rate)
|
|
{
|
|
struct samsung_vclk *vclk = to_vclk(hw);
|
|
unsigned long ret = 0;
|
|
|
|
/* Call cal api to recalculate rate */
|
|
ret = cal_dfs_get_rate(vclk->id);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int cal_vclk_dfs_set_rate(struct clk_hw *hw, unsigned long rate, unsigned long prate)
|
|
{
|
|
struct samsung_vclk *vclk = to_vclk(hw);
|
|
unsigned long flags = 0;
|
|
int ret = 0;
|
|
|
|
if (vclk->lock)
|
|
spin_lock_irqsave(vclk->lock, flags);
|
|
|
|
dbg_snapshot_clk(hw, __func__, rate, DSS_FLAG_IN);
|
|
/* Call cal api to set rate of clock */
|
|
ret = cal_dfs_set_rate(vclk->id, rate);
|
|
if (ret) {
|
|
pr_err("[CAL]%s failed %d %lu %d.\n", __func__,
|
|
vclk->id, rate, ret);
|
|
dbg_snapshot_clk(hw, __func__, rate, DSS_FLAG_ON);
|
|
if (vclk->lock)
|
|
spin_unlock_irqrestore(vclk->lock, flags);
|
|
return -EAGAIN;
|
|
}
|
|
dbg_snapshot_clk(hw, __func__, rate, DSS_FLAG_OUT);
|
|
|
|
if (vclk->lock)
|
|
spin_unlock_irqrestore(vclk->lock, flags);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int cal_vclk_dfs_set_rate_switch(struct clk_hw *hw, unsigned long rate, unsigned long prate)
|
|
{
|
|
struct samsung_vclk *vclk = to_vclk(hw);
|
|
unsigned long flags = 0;
|
|
int ret = 0;
|
|
|
|
if (vclk->lock)
|
|
spin_lock_irqsave(vclk->lock, flags);
|
|
|
|
/* Call cal api to set rate of clock */
|
|
ret = cal_dfs_set_rate_switch(vclk->id, rate);
|
|
if (ret) {
|
|
pr_err("[CAL]%s failed.\n", __func__);
|
|
if (vclk->lock)
|
|
spin_unlock_irqrestore(vclk->lock, flags);
|
|
return -EAGAIN;
|
|
}
|
|
|
|
if (vclk->lock)
|
|
spin_unlock_irqrestore(vclk->lock, flags);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int cal_vclk_qch_init(struct clk_hw *hw)
|
|
{
|
|
struct samsung_vclk *vclk = to_vclk(hw);
|
|
int ret = 0;
|
|
|
|
if (vclk->flags & VCLK_QCH_EN)
|
|
ret = cal_qch_init(vclk->id, 1);
|
|
else if (vclk->flags & VCLK_QCH_DIS)
|
|
ret = cal_qch_init(vclk->id, 0);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int cal_vclk_qactive_enable(struct clk_hw *hw)
|
|
{
|
|
struct samsung_vclk *vclk = to_vclk(hw);
|
|
unsigned long flags = 0;
|
|
unsigned int reg;
|
|
|
|
if (!(vclk->flags & VCLK_QACTIVE))
|
|
return 0;
|
|
|
|
if (!vclk->addr)
|
|
return 0;
|
|
|
|
if (vclk->lock)
|
|
spin_lock_irqsave(vclk->lock, flags);
|
|
|
|
dbg_snapshot_clk(hw, __func__, 1, DSS_FLAG_IN);
|
|
|
|
reg = readl(vclk->addr);
|
|
reg &= ~(vclk->mask);
|
|
reg |= vclk->val;
|
|
writel(reg, vclk->addr);
|
|
|
|
dbg_snapshot_clk(hw, __func__, 1, DSS_FLAG_OUT);
|
|
|
|
if (vclk->lock)
|
|
spin_unlock_irqrestore(vclk->lock, flags);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void cal_vclk_qactive_disable(struct clk_hw *hw)
|
|
{
|
|
struct samsung_vclk *vclk = to_vclk(hw);
|
|
unsigned long flags = 0;
|
|
unsigned int reg;
|
|
|
|
if (!(vclk->flags & VCLK_QACTIVE))
|
|
return ;
|
|
|
|
if (!vclk->addr)
|
|
return ;;
|
|
|
|
if (vclk->lock)
|
|
spin_lock_irqsave(vclk->lock, flags);
|
|
|
|
dbg_snapshot_clk(hw, __func__, 0, DSS_FLAG_IN);
|
|
|
|
reg = readl(vclk->addr);
|
|
reg &= ~(vclk->mask);
|
|
writel(reg, vclk->addr);
|
|
|
|
dbg_snapshot_clk(hw, __func__, 0, DSS_FLAG_OUT);
|
|
|
|
if (vclk->lock)
|
|
spin_unlock_irqrestore(vclk->lock, flags);
|
|
}
|
|
|
|
static const struct clk_ops samsung_vclk_ops = {
|
|
.enable = cal_vclk_enable,
|
|
.disable = cal_vclk_disable,
|
|
.is_enabled = cal_vclk_is_enabled,
|
|
.recalc_rate = cal_vclk_recalc_rate,
|
|
.round_rate = cal_vclk_round_rate,
|
|
.set_rate = cal_vclk_set_rate,
|
|
};
|
|
|
|
static const struct clk_ops samsung_vclk_dfs_ops = {
|
|
.recalc_rate = cal_vclk_dfs_recalc_rate,
|
|
.round_rate = cal_vclk_round_rate,
|
|
.set_rate = cal_vclk_dfs_set_rate,
|
|
};
|
|
|
|
static const struct clk_ops samsung_vclk_dfs_sw_ops = {
|
|
.recalc_rate = cal_vclk_dfs_sw_recalc_rate,
|
|
.round_rate = cal_vclk_round_rate,
|
|
.set_rate = cal_vclk_dfs_set_rate_switch,
|
|
};
|
|
|
|
static const struct clk_ops samsung_vclk_gate_ops = {
|
|
.enable = cal_vclk_enable,
|
|
.disable = cal_vclk_disable,
|
|
.is_enabled = cal_vclk_is_enabled,
|
|
.recalc_rate = cal_vclk_gate_recalc_rate,
|
|
};
|
|
|
|
static const struct clk_ops samsung_vclk_qactive_ops = {
|
|
.enable = cal_vclk_qactive_enable,
|
|
.disable = cal_vclk_qactive_disable,
|
|
};
|
|
|
|
static struct clk * __init _samsung_register_vclk(struct init_vclk *list)
|
|
{
|
|
struct samsung_vclk *vclk;
|
|
struct clk *clk;
|
|
struct clk_init_data init;
|
|
|
|
vclk = kzalloc(sizeof(struct samsung_vclk), GFP_KERNEL);
|
|
if (!vclk) {
|
|
pr_err("%s: could not allocate struct vclk\n", __func__);
|
|
return ERR_PTR(-ENOMEM);
|
|
}
|
|
|
|
init.name = list->name;
|
|
if (list->vclk_flags & VCLK_DFS)
|
|
init.ops = &samsung_vclk_dfs_ops;
|
|
else if (list->vclk_flags & VCLK_DFS_SWITCH)
|
|
init.ops = &samsung_vclk_dfs_sw_ops;
|
|
else if (list->vclk_flags & VCLK_GATE)
|
|
init.ops = &samsung_vclk_gate_ops;
|
|
else if (list->vclk_flags & VCLK_QACTIVE) {
|
|
init.ops = &samsung_vclk_qactive_ops;
|
|
vclk->addr = ioremap(list->addr, 4);
|
|
vclk->mask = list->mask;
|
|
vclk->val = list->val;
|
|
} else
|
|
init.ops = &samsung_vclk_ops;
|
|
init.flags = list->flags | (CLK_IS_BASIC | CLK_GET_RATE_NOCACHE | CLK_IGNORE_UNUSED);
|
|
init.parent_names = (list->parent ? &list->parent : NULL);
|
|
init.num_parents = (list->parent ? 1 : 0);
|
|
vclk->id = list->calid;
|
|
/* Flags for vclk are not defined yet */
|
|
vclk->flags = list->vclk_flags;
|
|
vclk->lock = &lock;
|
|
vclk->hw.init = &init;
|
|
clk = clk_register(NULL, &vclk->hw);
|
|
|
|
if (IS_ERR(clk))
|
|
kfree(vclk);
|
|
|
|
return clk;
|
|
}
|
|
|
|
void __init samsung_register_vclk(struct samsung_clk_provider *ctx,
|
|
struct init_vclk *list, unsigned int nr_vclk)
|
|
{
|
|
struct clk *clk;
|
|
int cnt;
|
|
unsigned int ret = 0;
|
|
|
|
for (cnt = 0; cnt < nr_vclk; cnt++) {
|
|
clk = _samsung_register_vclk(&list[cnt]);
|
|
if (IS_ERR(clk)) {
|
|
pr_err("%s: failed to register virtual clock %s\n",
|
|
__func__, (&list[cnt])->name);
|
|
continue;
|
|
}
|
|
|
|
samsung_clk_add_lookup(ctx, clk, (&list[cnt])->id);
|
|
|
|
/* Additional array of clocks for finding struct clk */
|
|
if ((&list[cnt])->alias) {
|
|
ret = clk_register_clkdev(clk, (&list[cnt])->alias, NULL);
|
|
if (ret)
|
|
pr_err("%s: failed to register lookup %s\n",
|
|
__func__, (&list[cnt])->alias);
|
|
}
|
|
|
|
cal_vclk_qch_init(__clk_get_hw(clk));
|
|
}
|
|
}
|