207 lines
4.4 KiB
C
207 lines
4.4 KiB
C
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||
|
/*
|
||
|
* Copyright (C) 2016 Free Electrons
|
||
|
* Copyright (C) 2016 NextThing Co
|
||
|
*
|
||
|
* Maxime Ripard <maxime.ripard@free-electrons.com>
|
||
|
*/
|
||
|
|
||
|
#include <linux/clk-provider.h>
|
||
|
#include <linux/regmap.h>
|
||
|
|
||
|
#include "sun4i_tcon.h"
|
||
|
#include "sun4i_tcon_dclk.h"
|
||
|
|
||
|
struct sun4i_dclk {
|
||
|
struct clk_hw hw;
|
||
|
struct regmap *regmap;
|
||
|
struct sun4i_tcon *tcon;
|
||
|
};
|
||
|
|
||
|
static inline struct sun4i_dclk *hw_to_dclk(struct clk_hw *hw)
|
||
|
{
|
||
|
return container_of(hw, struct sun4i_dclk, hw);
|
||
|
}
|
||
|
|
||
|
static void sun4i_dclk_disable(struct clk_hw *hw)
|
||
|
{
|
||
|
struct sun4i_dclk *dclk = hw_to_dclk(hw);
|
||
|
|
||
|
regmap_update_bits(dclk->regmap, SUN4I_TCON0_DCLK_REG,
|
||
|
BIT(SUN4I_TCON0_DCLK_GATE_BIT), 0);
|
||
|
}
|
||
|
|
||
|
static int sun4i_dclk_enable(struct clk_hw *hw)
|
||
|
{
|
||
|
struct sun4i_dclk *dclk = hw_to_dclk(hw);
|
||
|
|
||
|
return regmap_update_bits(dclk->regmap, SUN4I_TCON0_DCLK_REG,
|
||
|
BIT(SUN4I_TCON0_DCLK_GATE_BIT),
|
||
|
BIT(SUN4I_TCON0_DCLK_GATE_BIT));
|
||
|
}
|
||
|
|
||
|
static int sun4i_dclk_is_enabled(struct clk_hw *hw)
|
||
|
{
|
||
|
struct sun4i_dclk *dclk = hw_to_dclk(hw);
|
||
|
u32 val;
|
||
|
|
||
|
regmap_read(dclk->regmap, SUN4I_TCON0_DCLK_REG, &val);
|
||
|
|
||
|
return val & BIT(SUN4I_TCON0_DCLK_GATE_BIT);
|
||
|
}
|
||
|
|
||
|
static unsigned long sun4i_dclk_recalc_rate(struct clk_hw *hw,
|
||
|
unsigned long parent_rate)
|
||
|
{
|
||
|
struct sun4i_dclk *dclk = hw_to_dclk(hw);
|
||
|
u32 val;
|
||
|
|
||
|
regmap_read(dclk->regmap, SUN4I_TCON0_DCLK_REG, &val);
|
||
|
|
||
|
val >>= SUN4I_TCON0_DCLK_DIV_SHIFT;
|
||
|
val &= (1 << SUN4I_TCON0_DCLK_DIV_WIDTH) - 1;
|
||
|
|
||
|
if (!val)
|
||
|
val = 1;
|
||
|
|
||
|
return parent_rate / val;
|
||
|
}
|
||
|
|
||
|
static long sun4i_dclk_round_rate(struct clk_hw *hw, unsigned long rate,
|
||
|
unsigned long *parent_rate)
|
||
|
{
|
||
|
struct sun4i_dclk *dclk = hw_to_dclk(hw);
|
||
|
struct sun4i_tcon *tcon = dclk->tcon;
|
||
|
unsigned long best_parent = 0;
|
||
|
u8 best_div = 1;
|
||
|
int i;
|
||
|
|
||
|
for (i = tcon->dclk_min_div; i <= tcon->dclk_max_div; i++) {
|
||
|
u64 ideal = (u64)rate * i;
|
||
|
unsigned long rounded;
|
||
|
|
||
|
/*
|
||
|
* ideal has overflowed the max value that can be stored in an
|
||
|
* unsigned long, and every clk operation we might do on a
|
||
|
* truncated u64 value will give us incorrect results.
|
||
|
* Let's just stop there since bigger dividers will result in
|
||
|
* the same overflow issue.
|
||
|
*/
|
||
|
if (ideal > ULONG_MAX)
|
||
|
goto out;
|
||
|
|
||
|
rounded = clk_hw_round_rate(clk_hw_get_parent(hw),
|
||
|
ideal);
|
||
|
|
||
|
if (rounded == ideal) {
|
||
|
best_parent = rounded;
|
||
|
best_div = i;
|
||
|
goto out;
|
||
|
}
|
||
|
|
||
|
if (abs(rate - rounded / i) <
|
||
|
abs(rate - best_parent / best_div)) {
|
||
|
best_parent = rounded;
|
||
|
best_div = i;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
out:
|
||
|
*parent_rate = best_parent;
|
||
|
|
||
|
return best_parent / best_div;
|
||
|
}
|
||
|
|
||
|
static int sun4i_dclk_set_rate(struct clk_hw *hw, unsigned long rate,
|
||
|
unsigned long parent_rate)
|
||
|
{
|
||
|
struct sun4i_dclk *dclk = hw_to_dclk(hw);
|
||
|
u8 div = parent_rate / rate;
|
||
|
|
||
|
return regmap_update_bits(dclk->regmap, SUN4I_TCON0_DCLK_REG,
|
||
|
GENMASK(6, 0), div);
|
||
|
}
|
||
|
|
||
|
static int sun4i_dclk_get_phase(struct clk_hw *hw)
|
||
|
{
|
||
|
struct sun4i_dclk *dclk = hw_to_dclk(hw);
|
||
|
u32 val;
|
||
|
|
||
|
regmap_read(dclk->regmap, SUN4I_TCON0_IO_POL_REG, &val);
|
||
|
|
||
|
val >>= 28;
|
||
|
val &= 3;
|
||
|
|
||
|
return val * 120;
|
||
|
}
|
||
|
|
||
|
static int sun4i_dclk_set_phase(struct clk_hw *hw, int degrees)
|
||
|
{
|
||
|
struct sun4i_dclk *dclk = hw_to_dclk(hw);
|
||
|
u32 val = degrees / 120;
|
||
|
|
||
|
val <<= 28;
|
||
|
|
||
|
regmap_update_bits(dclk->regmap, SUN4I_TCON0_IO_POL_REG,
|
||
|
GENMASK(29, 28),
|
||
|
val);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static const struct clk_ops sun4i_dclk_ops = {
|
||
|
.disable = sun4i_dclk_disable,
|
||
|
.enable = sun4i_dclk_enable,
|
||
|
.is_enabled = sun4i_dclk_is_enabled,
|
||
|
|
||
|
.recalc_rate = sun4i_dclk_recalc_rate,
|
||
|
.round_rate = sun4i_dclk_round_rate,
|
||
|
.set_rate = sun4i_dclk_set_rate,
|
||
|
|
||
|
.get_phase = sun4i_dclk_get_phase,
|
||
|
.set_phase = sun4i_dclk_set_phase,
|
||
|
};
|
||
|
|
||
|
int sun4i_dclk_create(struct device *dev, struct sun4i_tcon *tcon)
|
||
|
{
|
||
|
const char *clk_name, *parent_name;
|
||
|
struct clk_init_data init;
|
||
|
struct sun4i_dclk *dclk;
|
||
|
int ret;
|
||
|
|
||
|
parent_name = __clk_get_name(tcon->sclk0);
|
||
|
ret = of_property_read_string_index(dev->of_node,
|
||
|
"clock-output-names", 0,
|
||
|
&clk_name);
|
||
|
if (ret)
|
||
|
return ret;
|
||
|
|
||
|
dclk = devm_kzalloc(dev, sizeof(*dclk), GFP_KERNEL);
|
||
|
if (!dclk)
|
||
|
return -ENOMEM;
|
||
|
dclk->tcon = tcon;
|
||
|
|
||
|
init.name = clk_name;
|
||
|
init.ops = &sun4i_dclk_ops;
|
||
|
init.parent_names = &parent_name;
|
||
|
init.num_parents = 1;
|
||
|
init.flags = CLK_SET_RATE_PARENT;
|
||
|
|
||
|
dclk->regmap = tcon->regs;
|
||
|
dclk->hw.init = &init;
|
||
|
|
||
|
tcon->dclk = clk_register(dev, &dclk->hw);
|
||
|
if (IS_ERR(tcon->dclk))
|
||
|
return PTR_ERR(tcon->dclk);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
EXPORT_SYMBOL(sun4i_dclk_create);
|
||
|
|
||
|
int sun4i_dclk_free(struct sun4i_tcon *tcon)
|
||
|
{
|
||
|
clk_unregister(tcon->dclk);
|
||
|
return 0;
|
||
|
}
|
||
|
EXPORT_SYMBOL(sun4i_dclk_free);
|