blob: 7a5ee7a45fcca1aa01f37721641b521ca73e87de [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2019 DENX Software Engineering
* Lukasz Majewski, DENX Software Engineering, lukma@denx.de
*
* Copyright (C) 2011 Sascha Hauer, Pengutronix <s.hauer@pengutronix.de>
* Copyright (C) 2011 Richard Zhao, Linaro <richard.zhao@linaro.org>
* Copyright (C) 2011-2012 Mike Turquette, Linaro Ltd <mturquette@linaro.org>
*
* Simple multiplexer clock implementation
*/
/*
* U-Boot CCF porting node:
*
* The Linux kernel - as of tag: 5.0-rc3 is using also the imx_clk_fixup_mux()
* version of CCF mux. It is used on e.g. imx6q to provide fixes (like
* imx_cscmr1_fixup) for broken HW.
*
* At least for IMX6Q (but NOT IMX6QP) it is important when we set the parent
* clock.
*/
#include <common.h>
#include <clk.h>
#include <clk-uclass.h>
#include <dm/device.h>
#include <dm/devres.h>
#include <dm/uclass.h>
#include <linux/bitops.h>
#include <malloc.h>
#include <asm/io.h>
#include <linux/clk-provider.h>
#include <linux/err.h>
#include "clk.h"
#define UBOOT_DM_CLK_CCF_MUX "ccf_clk_mux"
int clk_mux_val_to_index(struct clk *clk, u32 *table, unsigned int flags,
unsigned int val)
{
struct clk_mux *mux = to_clk_mux(clk);
int num_parents = mux->num_parents;
if (table) {
int i;
for (i = 0; i < num_parents; i++)
if (table[i] == val)
return i;
return -EINVAL;
}
if (val && (flags & CLK_MUX_INDEX_BIT))
val = ffs(val) - 1;
if (val && (flags & CLK_MUX_INDEX_ONE))
val--;
if (val >= num_parents)
return -EINVAL;
return val;
}
unsigned int clk_mux_index_to_val(u32 *table, unsigned int flags, u8 index)
{
unsigned int val = index;
if (table) {
val = table[index];
} else {
if (flags & CLK_MUX_INDEX_BIT)
val = 1 << index;
if (flags & CLK_MUX_INDEX_ONE)
val++;
}
return val;
}
u8 clk_mux_get_parent(struct clk *clk)
{
struct clk_mux *mux = to_clk_mux(clk);
u32 val;
#if CONFIG_IS_ENABLED(SANDBOX_CLK_CCF)
val = mux->io_mux_val;
#else
val = readl(mux->reg);
#endif
val >>= mux->shift;
val &= mux->mask;
return clk_mux_val_to_index(clk, mux->table, mux->flags, val);
}
static int clk_fetch_parent_index(struct clk *clk,
struct clk *parent)
{
struct clk_mux *mux = to_clk_mux(clk);
int i;
if (!parent)
return -EINVAL;
for (i = 0; i < mux->num_parents; i++) {
if (!strcmp(parent->dev->name, mux->parent_names[i]))
return i;
}
return -EINVAL;
}
static int 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_fetch_parent_index(clk, parent);
if (index < 0) {
printf("Could not fetch index\n");
return index;
}
val = clk_mux_index_to_val(mux->table, mux->flags, index);
if (mux->flags & CLK_MUX_HIWORD_MASK) {
reg = mux->mask << (mux->shift + 16);
} else {
#if CONFIG_IS_ENABLED(SANDBOX_CLK_CCF)
reg = mux->io_mux_val;
#else
reg = readl(mux->reg);
#endif
reg &= ~(mux->mask << mux->shift);
}
val = val << mux->shift;
reg |= val;
#if CONFIG_IS_ENABLED(SANDBOX_CLK_CCF)
mux->io_mux_val = reg;
#else
writel(reg, mux->reg);
#endif
return 0;
}
static ulong clk_mux_get_rate(struct clk *clk)
{
struct clk_mux *mux = to_clk_mux(clk_dev_binded(clk) ?
dev_get_clk_ptr(clk->dev) : clk);
struct udevice *parent;
struct clk *pclk;
int err, index;
index = clk_mux_get_parent(clk);
if (index >= mux->num_parents)
return -EFAULT;
err = uclass_get_device_by_name(UCLASS_CLK, mux->parent_names[index],
&parent);
if (err)
return err;
pclk = dev_get_clk_ptr(parent);
if (!pclk)
return -ENODEV;
return clk_get_rate(pclk);
}
const struct clk_ops clk_mux_ops = {
.get_rate = clk_mux_get_rate,
.set_parent = clk_mux_set_parent,
};
struct clk *clk_hw_register_mux_table(struct device *dev, const char *name,
const char * const *parent_names, u8 num_parents,
unsigned long flags,
void __iomem *reg, u8 shift, u32 mask,
u8 clk_mux_flags, u32 *table)
{
struct clk_mux *mux;
struct clk *clk;
u8 width = 0;
int ret;
if (clk_mux_flags & CLK_MUX_HIWORD_MASK) {
width = fls(mask) - ffs(mask) + 1;
if (width + shift > 16) {
pr_err("mux value exceeds LOWORD field\n");
return ERR_PTR(-EINVAL);
}
}
/* allocate the mux */
mux = kzalloc(sizeof(*mux), GFP_KERNEL);
if (!mux)
return ERR_PTR(-ENOMEM);
/* U-boot specific assignments */
mux->parent_names = parent_names;
mux->num_parents = num_parents;
/* struct clk_mux assignments */
mux->reg = reg;
mux->shift = shift;
mux->mask = mask;
mux->flags = clk_mux_flags;
mux->table = table;
#if CONFIG_IS_ENABLED(SANDBOX_CLK_CCF)
mux->io_mux_val = *(u32 *)reg;
#endif
clk = &mux->clk;
clk->flags = flags;
/*
* Read the current mux setup - so we assign correct parent.
*
* Changing parent would require changing internals of udevice struct
* for the corresponding clock (to do that define .set_parent() method).
*/
ret = clk_register(clk, UBOOT_DM_CLK_CCF_MUX, name,
parent_names[clk_mux_get_parent(clk)]);
if (ret) {
kfree(mux);
return ERR_PTR(ret);
}
return clk;
}
struct clk *clk_register_mux_table(struct device *dev, const char *name,
const char * const *parent_names, u8 num_parents,
unsigned long flags,
void __iomem *reg, u8 shift, u32 mask,
u8 clk_mux_flags, u32 *table)
{
struct clk *clk;
clk = clk_hw_register_mux_table(dev, name, parent_names, num_parents,
flags, reg, shift, mask, clk_mux_flags,
table);
if (IS_ERR(clk))
return ERR_CAST(clk);
return clk;
}
struct clk *clk_register_mux(struct device *dev, const char *name,
const char * const *parent_names, u8 num_parents,
unsigned long flags,
void __iomem *reg, u8 shift, u8 width,
u8 clk_mux_flags)
{
u32 mask = BIT(width) - 1;
return clk_register_mux_table(dev, name, parent_names, num_parents,
flags, reg, shift, mask, clk_mux_flags,
NULL);
}
U_BOOT_DRIVER(ccf_clk_mux) = {
.name = UBOOT_DM_CLK_CCF_MUX,
.id = UCLASS_CLK,
.ops = &clk_mux_ops,
.flags = DM_FLAG_PRE_RELOC,
};