blob: 37c093e0cf823b592a931a409555c7f6813661a5 [file] [log] [blame]
/*
* 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));
}
}