blob: 0d3096d1e4ec21dd09000ddbfbf2aa554213aa76 [file] [log] [blame]
/*
* Copyright (C) 2013 Imagination Technologies 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.
*
* Metag simple multiplexer clock implementation
* Based on simple multiplexer clock implementation, but does appropriate
* locking to protect registers shared between hardware threads.
*
*/
#include <linux/clk-provider.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/slab.h>
#include <asm/global_lock.h>
/**
* struct clk_metag_mux - metag multiplexer clock
*
* @mux: the parent class
* @ops: pointer to clk_ops of parent class
*
* Clock with multiple selectable parents. Extends basic mux by using a global
* exclusive lock when read-modify-writing the mux field so that multiple
* threads/cores can use different fields in the same register.
*/
struct clk_metag_mux {
struct clk_mux mux;
const struct clk_ops *ops;
};
static inline struct clk_metag_mux *to_clk_metag_mux(struct clk_hw *hw)
{
struct clk_mux *mux = container_of(hw, struct clk_mux, hw);
return container_of(mux, struct clk_metag_mux, mux);
}
static u8 clk_metag_mux_get_parent(struct clk_hw *hw)
{
struct clk_metag_mux *mux = to_clk_metag_mux(hw);
return mux->ops->get_parent(&mux->mux.hw);
}
/* Acquire exclusive lock since other cores may access the same register */
static int clk_metag_mux_set_parent(struct clk_hw *hw, u8 index)
{
struct clk_metag_mux *mux = to_clk_metag_mux(hw);
int ret;
unsigned long flags;
__global_lock2(flags);
ret = mux->ops->set_parent(&mux->mux.hw, index);
__global_unlock2(flags);
return ret;
}
static long clk_metag_mux_determine_rate(struct clk_hw *hw, unsigned long rate,
unsigned long *prate,
struct clk **best_parent)
{
struct clk_metag_mux *mux = to_clk_metag_mux(hw);
return mux->ops->determine_rate(&mux->mux.hw, rate, prate, best_parent);
}
static const struct clk_ops clk_metag_mux_ops = {
.get_parent = clk_metag_mux_get_parent,
.set_parent = clk_metag_mux_set_parent,
.determine_rate = clk_metag_mux_determine_rate,
};
static struct clk *__init clk_register_metag_mux(struct device *dev,
const char *name, const char **parent_names, u8 num_parents,
s32 default_parent, unsigned long flags, void __iomem *reg,
u8 shift, u8 width, u8 clk_mux_flags)
{
struct clk_metag_mux *mux;
struct clk *clk;
struct clk_init_data init;
/* allocate the mux */
mux = kzalloc(sizeof(struct clk_metag_mux), GFP_KERNEL);
if (!mux) {
pr_err("%s: could not allocate metag mux clk\n", __func__);
return ERR_PTR(-ENOMEM);
}
init.name = name;
init.ops = &clk_metag_mux_ops;
init.flags = flags | CLK_IS_BASIC;
init.parent_names = parent_names;
init.num_parents = num_parents;
/* struct clk_mux assignments */
mux->mux.reg = reg;
mux->mux.shift = shift;
mux->mux.mask = BIT(width) - 1;
mux->mux.flags = clk_mux_flags;
mux->mux.hw.init = &init;
/* struct clk_metag_mux assignments */
mux->ops = &clk_mux_ops;
/* set default value */
if (default_parent >= 0)
clk_metag_mux_set_parent(&mux->mux.hw, default_parent);
clk = clk_register(dev, &mux->mux.hw);
if (IS_ERR(clk))
kfree(mux);
return clk;
}
#ifdef CONFIG_OF
/**
* of_metag_mux_clk_setup() - Setup function for simple fixed rate clock
*/
static void __init of_metag_mux_clk_setup(struct device_node *node)
{
struct clk *clk;
const char *clk_name = node->name;
u32 shift, width, default_clock;
void __iomem *reg;
int len, i;
struct property *prop;
const char **parent_names;
unsigned int num_parents;
u8 flags = 0;
unsigned long clk_flags = 0;
of_property_read_string(node, "clock-output-names", &clk_name);
if (of_property_read_u32(node, "shift", &shift)) {
pr_err("%s(%s): could not read shift property\n",
__func__, clk_name);
return;
}
if (of_property_read_u32(node, "width", &width)) {
pr_err("%s(%s): could not read width property\n",
__func__, clk_name);
return;
}
/* count maximum number of parent clocks */
prop = of_find_property(node, "clocks", &len);
if (!prop) {
pr_err("%s(%s): could not find clocks property\n",
__func__, clk_name);
return;
}
/*
* There cannot be more parents than entries in "clocks" property (which
* may include additional args too. It also needs to fit in a u8.
*/
num_parents = len / sizeof(u32);
num_parents = min(num_parents, 0xffu);
/* allocate an array of parent names */
parent_names = kzalloc(sizeof(const char *)*num_parents, GFP_KERNEL);
if (!parent_names) {
pr_err("%s(%s): could not allocate %u parent names\n",
__func__, clk_name, num_parents);
goto err_kfree;
}
/* fill in the parent names */
for (i = 0; i < num_parents; ++i) {
parent_names[i] = of_clk_get_parent_name(node, i);
if (!parent_names[i]) {
/* truncate array length if we hit the end early */
num_parents = i;
break;
}
}
/* default parent clock (mux value) */
if (!of_property_read_u32(node, "default-clock", &default_clock)) {
if (default_clock >= num_parents) {
pr_err("%s(%s): default-clock %u out of range (%u bits)\n",
__func__, clk_name, default_clock, width);
goto err_kfree;
}
} else {
default_clock = -1;
}
reg = of_iomap(node, 0);
if (!reg) {
pr_err("%s(%s): of_iomap failed\n",
__func__, clk_name);
goto err_kfree;
}
if (of_find_property(node, "linux,clk-set-rate-parent", NULL))
clk_flags |= CLK_SET_RATE_PARENT;
if (of_find_property(node, "linux,clk-set-rate-remux", NULL))
clk_flags |= CLK_SET_RATE_REMUX;
clk = clk_register_metag_mux(NULL, clk_name, parent_names, num_parents,
default_clock, clk_flags, reg, shift,
width, flags);
if (IS_ERR(clk))
goto err_iounmap;
of_clk_add_provider(node, of_clk_src_simple_get, clk);
return;
err_iounmap:
iounmap(reg);
err_kfree:
kfree(parent_names);
}
CLK_OF_DECLARE(metag_mux_clk, "img,meta-mux-clock", of_metag_mux_clk_setup);
#endif /* CONFIG_OF */