/*
* Copyright (C) 2013 Broadcom Corporation
* Copyright 2013 Linaro Limited
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation version 2.
*
* This program is distributed "as is" WITHOUT ANY WARRANTY of any
* kind, whether express or implied; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/io.h>
#include <linux/of_address.h>
#include "clk-kona.h"
/* These are used when a selector or trigger is found to be unneeded */
#define selector_clear_exists(sel) ((sel)->width = 0)
#define trigger_clear_exists(trig) FLAG_CLEAR(trig, TRIG, EXISTS)
/* Validity checking */
static bool ccu_data_offsets_valid(struct ccu_data *ccu)
{
struct ccu_policy *ccu_policy = &ccu->policy;
u32 limit;
limit = ccu->range - sizeof(u32);
limit = round_down(limit, sizeof(u32));
if (ccu_policy_exists(ccu_policy)) {
if (ccu_policy->enable.offset > limit) {
pr_err("%s: bad policy enable offset for %s "
"(%u > %u)\n", __func__,
ccu->name, ccu_policy->enable.offset, limit);
return false;
}
if (ccu_policy->control.offset > limit) {
pr_err("%s: bad policy control offset for %s "
"(%u > %u)\n", __func__,
ccu->name, ccu_policy->control.offset, limit);
return false;
}
}
return true;
}
static bool clk_requires_trigger(struct kona_clk *bcm_clk)
{
struct peri_clk_data *peri = bcm_clk->u.peri;
struct bcm_clk_sel *sel;
struct bcm_clk_div *div;
if (bcm_clk->type != bcm_clk_peri)
return false;
sel = &peri->sel;
if (sel->parent_count && selector_exists(sel))
return true;
div = &peri->div;
if (!divider_exists(div))
return false;
/* Fixed dividers don't need triggers */
if (!divider_is_fixed(div))
return true;
div = &peri->pre_div;
return divider_exists(div) && !divider_is_fixed(div);
}
static bool peri_clk_data_offsets_valid(struct kona_clk *bcm_clk)
{
struct peri_clk_data *peri;
struct bcm_clk_policy *policy;
struct bcm_clk_gate *gate;
struct bcm_clk_hyst *hyst;
struct bcm_clk_div *div;
struct bcm_clk_sel *sel;
struct bcm_clk_trig *trig;
const char *name;
u32 range;
u32 limit;
BUG_ON(bcm_clk->type != bcm_clk_peri);
peri = bcm_clk->u.peri;
name = bcm_clk->init_data.name;
range = bcm_clk->ccu->range;
limit = range - sizeof(u32);
limit = round_down(limit, sizeof(u32));
policy = &peri->policy;
if (policy_exists(policy)) {
if (policy->offset > limit) {
pr_err("%s: bad policy offset for %s (%u > %u)\n",
__func__, name, policy->offset, limit);
return false;
}
}
gate = &peri->gate;
hyst = &peri->hyst;
if (gate_exists(gate)) {
if (gate->offset > limit) {
pr_err("%s: bad gate offset for %s (%u > %u)\n",
__func__, name, gate->offset, limit);
return false;
}
if (hyst_exists(hyst)) {
if (hyst->offset > limit) {
pr_err("%s: bad hysteresis offset for %s "
"(%u > %u)\n", __func__,
name, hyst->offset, limit);
return false;
}
}
} else if (hyst_exists(hyst)) {
pr_err("%s: hysteresis but no gate for %s\n", __func__, name);
return false;
}
div = &peri->div;
if (divider_exists(div)) {
if (div->u.s.offset > limit) {
pr_err("%s: bad divider offset for %s (%u > %u)\n",
__func__, name, div->u.s.offset, limit);
return false;
}
}
div = &peri->pre_div;
if (divider_exists(div)) {
if (div->u.s.offset > limit) {
pr_err("%s: bad pre-divider offset for %s "
"(%u > %u)\n",
__func__, name, div->u.s.offset, limit);
return false;
}
}
sel = &peri->sel;
if (selector_exists(sel)) {
if (sel->offset > limit) {
pr_err("%s: bad selector offset for %s (%u > %u)\n",
__func__, name, sel->offset, limit);
return false;
}
}
trig = &peri->trig;
if (trigger_exists(trig)) {
if (trig->offset > limit) {
pr_err(