blob: 22db67df79b3c05c9012f311fe3f5a6451493748 [file] [log] [blame]
/*
* Copyright (C) 2013-2014 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.
*
* TZ1090 Divider Clock.
*/
#include <linux/clk-provider.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/slab.h>
#include <asm/global_lock.h>
#include "clk.h"
/**
* struct tz1090_clk_div_priv - tz1090 divider clock
*
* @div: the parent class
* @ops: pointer to clk_ops of parent class
*
* Divider clock whose field shares a register with other fields which may be
* used by multiple threads/cores and other drivers.
*/
struct tz1090_clk_div_priv {
struct clk_divider div;
const struct clk_ops *ops;
};
static inline struct tz1090_clk_div_priv *to_tz1090_clk_div(struct clk_hw *hw)
{
struct clk_divider *div = container_of(hw, struct clk_divider, hw);
return container_of(div, struct tz1090_clk_div_priv, div);
}
static unsigned long tz1090_clk_divider_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
struct tz1090_clk_div_priv *div = to_tz1090_clk_div(hw);
return div->ops->recalc_rate(&div->div.hw, parent_rate);
}
static long tz1090_clk_divider_round_rate(struct clk_hw *hw, unsigned long rate,
unsigned long *prate)
{
struct tz1090_clk_div_priv *div = to_tz1090_clk_div(hw);
return div->ops->round_rate(&div->div.hw, rate, prate);
}
/* Acquire exclusive lock since other cores may access the same register */
static int tz1090_clk_divider_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
struct tz1090_clk_div_priv *div = to_tz1090_clk_div(hw);
int ret;
unsigned long flags;
__global_lock2(flags);
ret = div->ops->set_rate(&div->div.hw, rate, parent_rate);
__global_unlock2(flags);
return ret;
}
static const struct clk_ops tz1090_clk_div_ops = {
.recalc_rate = tz1090_clk_divider_recalc_rate,
.round_rate = tz1090_clk_divider_round_rate,
.set_rate = tz1090_clk_divider_set_rate,
};
static struct clk *__init __register_divider(const char *name,
const char *parent_name,
unsigned long flags,
void __iomem *reg, u8 shift,
u8 width, u8 clk_divider_flags)
{
struct tz1090_clk_div_priv *div;
struct clk *clk;
struct clk_init_data init;
/* allocate the divider */
div = kzalloc(sizeof(struct tz1090_clk_div_priv), GFP_KERNEL);
if (!div)
return ERR_PTR(-ENOMEM);
init.name = name;
init.ops = &tz1090_clk_div_ops;
init.flags = flags | CLK_IS_BASIC;
init.parent_names = (parent_name ? &parent_name : NULL);
init.num_parents = (parent_name ? 1 : 0);
/* struct clk_divider assignments */
div->div.reg = reg;
div->div.shift = shift;
div->div.width = width;
div->div.flags = clk_divider_flags;
div->div.hw.init = &init;
/* struct tz1090_clk_div_priv assignments */
div->ops = &clk_divider_ops;
/* register the clock */
clk = clk_register(NULL, &div->div.hw);
if (IS_ERR(clk))
kfree(div);
return clk;
}
/**
* tz1090_clk_register_dividers() - Register set of dividers with a provider.
* @p: TZ1090 clock provider.
* @dividers: Array of divider descriptions.
* @count Number of dividers described in the array.
*/
void __init tz1090_clk_register_dividers(struct tz1090_clk_provider *p,
const struct tz1090_clk_divider *dividers,
unsigned int count)
{
const struct tz1090_clk_divider *div;
struct clk *clk;
unsigned int i;
for (div = dividers, i = 0; i < count; ++div, ++i) {
/*
* Dividers in registers shared between OSes must protect the
* register with a global lock. Others with dedicated registers
* can just use a normal divider.
*/
if (div->shared)
clk = __register_divider(tz1090_clk_xlate(p, div->name),
tz1090_clk_xlate(p, div->parent),
div->flags, p->base + div->reg,
div->shift, div->width, div->div_flags);
else
clk = clk_register_divider(NULL,
tz1090_clk_xlate(p, div->name),
tz1090_clk_xlate(p, div->parent),
div->flags, p->base + div->reg,
div->shift, div->width, div->div_flags,
NULL);
p->clk_data.clks[div->id] = clk;
}
}