blob: 64bffa3b181991ffdb8deb4ed35c534feb0c2a2e [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright 2019 NXP
*/
#include <log.h>
#include <asm/io.h>
#include <malloc.h>
#include <clk-uclass.h>
#include <dm/device.h>
#include <dm/devres.h>
#include <linux/clk-provider.h>
#include <clk.h>
#include "clk.h"
#include <linux/err.h>
#define UBOOT_DM_CLK_IMX_COMPOSITE "imx_clk_composite"
#define PCG_PREDIV_SHIFT 16
#define PCG_PREDIV_WIDTH 3
#define PCG_PREDIV_MAX 8
#define PCG_DIV_SHIFT 0
#define PCG_DIV_WIDTH 6
#define PCG_DIV_MAX 64
#define PCG_PCS_SHIFT 24
#define PCG_PCS_MASK 0x7
#define PCG_CGC_SHIFT 28
static unsigned long imx8m_clk_composite_divider_recalc_rate(struct clk *clk)
{
struct clk_divider *divider = (struct clk_divider *)to_clk_divider(clk);
struct clk_composite *composite = (struct clk_composite *)clk->data;
ulong parent_rate = clk_get_parent_rate(&composite->clk);
unsigned long prediv_rate;
unsigned int prediv_value;
unsigned int div_value;
debug("%s: name %s prate: %lu reg: %p\n", __func__,
(&composite->clk)->dev->name, parent_rate, divider->reg);
prediv_value = readl(divider->reg) >> divider->shift;
prediv_value &= clk_div_mask(divider->width);
prediv_rate = divider_recalc_rate(clk, parent_rate, prediv_value,
NULL, divider->flags,
divider->width);
div_value = readl(divider->reg) >> PCG_DIV_SHIFT;
div_value &= clk_div_mask(PCG_DIV_WIDTH);
return divider_recalc_rate(clk, prediv_rate, div_value, NULL,
divider->flags, PCG_DIV_WIDTH);
}
static int imx8m_clk_composite_compute_dividers(unsigned long rate,
unsigned long parent_rate,
int *prediv, int *postdiv)
{
int div1, div2;
int error = INT_MAX;
int ret = -EINVAL;
*prediv = 1;
*postdiv = 1;
for (div1 = 1; div1 <= PCG_PREDIV_MAX; div1++) {
for (div2 = 1; div2 <= PCG_DIV_MAX; div2++) {
int new_error = ((parent_rate / div1) / div2) - rate;
if (abs(new_error) < abs(error)) {
*prediv = div1;
*postdiv = div2;
error = new_error;
ret = 0;
}
}
}
return ret;
}
/*
* The clk are bound to a dev, because it is part of composite clk
* use composite clk to get dev
*/
static ulong imx8m_clk_composite_divider_set_rate(struct clk *clk,
unsigned long rate)
{
struct clk_divider *divider = (struct clk_divider *)to_clk_divider(clk);
struct clk_composite *composite = (struct clk_composite *)clk->data;
ulong parent_rate = clk_get_parent_rate(&composite->clk);
int prediv_value;
int div_value;
int ret;
u32 val;
ret = imx8m_clk_composite_compute_dividers(rate, parent_rate,
&prediv_value, &div_value);
if (ret)
return ret;
val = readl(divider->reg);
val &= ~((clk_div_mask(divider->width) << divider->shift) |
(clk_div_mask(PCG_DIV_WIDTH) << PCG_DIV_SHIFT));
val |= (u32)(prediv_value - 1) << divider->shift;
val |= (u32)(div_value - 1) << PCG_DIV_SHIFT;
writel(val, divider->reg);
return clk_get_rate(&composite->clk);
}
static const struct clk_ops imx8m_clk_composite_divider_ops = {
.get_rate = imx8m_clk_composite_divider_recalc_rate,
.set_rate = imx8m_clk_composite_divider_set_rate,
};
static int imx8m_clk_mux_set_parent(struct clk *clk, struct clk *parent)
{
struct clk_mux *mux = to_clk_mux(clk);
int index;
u32 val;
u32 reg;
index = clk_mux_fetch_parent_index(clk, parent);
if (index < 0) {
log_err("Could not fetch index\n");
return index;
}
val = clk_mux_index_to_val(mux->table, mux->flags, index);
reg = readl(mux->reg);
reg &= ~(mux->mask << mux->shift);
val = val << mux->shift;
reg |= val;
/*
* write twice to make sure non-target interface
* SEL_A/B point the same clk input.
*/
writel(reg, mux->reg);
writel(reg, mux->reg);
return 0;
}
const struct clk_ops imx8m_clk_mux_ops = {
.get_rate = clk_generic_get_rate,
.set_parent = imx8m_clk_mux_set_parent,
};
struct clk *imx8m_clk_composite_flags(const char *name,
const char * const *parent_names,
int num_parents, void __iomem *reg,
unsigned long flags)
{
struct clk *clk = ERR_PTR(-ENOMEM);
struct clk_divider *div = NULL;
struct clk_gate *gate = NULL;
struct clk_mux *mux = NULL;
mux = kzalloc(sizeof(*mux), GFP_KERNEL);
if (!mux)
goto fail;
mux->reg = reg;
mux->shift = PCG_PCS_SHIFT;
mux->mask = PCG_PCS_MASK;
mux->num_parents = num_parents;
mux->parent_names = parent_names;
div = kzalloc(sizeof(*div), GFP_KERNEL);
if (!div)
goto fail;
div->reg = reg;
div->shift = PCG_PREDIV_SHIFT;
div->width = PCG_PREDIV_WIDTH;
div->flags = CLK_DIVIDER_ROUND_CLOSEST;
gate = kzalloc(sizeof(*gate), GFP_KERNEL);
if (!gate)
goto fail;
gate->reg = reg;
gate->bit_idx = PCG_CGC_SHIFT;
clk = clk_register_composite(NULL, name,
parent_names, num_parents,
&mux->clk, &imx8m_clk_mux_ops, &div->clk,
&imx8m_clk_composite_divider_ops,
&gate->clk, &clk_gate_ops, flags);
if (IS_ERR(clk))
goto fail;
return clk;
fail:
kfree(gate);
kfree(div);
kfree(mux);
return ERR_CAST(clk);
}