blob: 1a8f0ca6f51534c5637ca4730e60ee1bfb8f7fe9 [file] [log] [blame]
/*
* Copyright (c) 2016-2017, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#define pr_fmt(fmt) "clk: %s: " fmt, __func__
#include <linux/clk.h>
#include "clk-voter.h"
static int voter_clk_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
int ret = 0;
struct clk_voter *v = to_clk_voter(hw);
unsigned long cur_rate, new_rate, other_rate = 0;
if (v->is_branch)
return ret;
if (v->enabled) {
struct clk_hw *parent = clk_hw_get_parent(hw);
if (!parent)
return -EINVAL;
/*
* Get the aggregate rate without this clock's vote and update
* if the new rate is different than the current rate.
*/
other_rate = clk_aggregate_rate(hw, parent->core);
cur_rate = max(other_rate, clk_get_rate(hw->clk));
new_rate = max(other_rate, rate);
if (new_rate != cur_rate) {
ret = clk_set_rate(parent->clk, new_rate);
if (ret)
return ret;
}
}
v->rate = rate;
return ret;
}
static int voter_clk_prepare(struct clk_hw *hw)
{
int ret = 0;
unsigned long cur_rate;
struct clk_hw *parent;
struct clk_voter *v = to_clk_voter(hw);
parent = clk_hw_get_parent(hw);
if (!parent)
return -EINVAL;
if (v->is_branch) {
v->enabled = true;
return ret;
}
/*
* Increase the rate if this clock is voting for a higher rate
* than the current rate.
*/
cur_rate = clk_aggregate_rate(hw, parent->core);
if (v->rate > cur_rate) {
ret = clk_set_rate(parent->clk, v->rate);
if (ret)
return ret;
}
v->enabled = true;
return ret;
}
static void voter_clk_unprepare(struct clk_hw *hw)
{
unsigned long cur_rate, new_rate;
struct clk_hw *parent;
struct clk_voter *v = to_clk_voter(hw);
parent = clk_hw_get_parent(hw);
if (!parent)
return;
/*
* Decrease the rate if this clock was the only one voting for
* the highest rate.
*/
v->enabled = false;
if (v->is_branch)
return;
new_rate = clk_aggregate_rate(hw, parent->core);
cur_rate = max(new_rate, v->rate);
if (new_rate < cur_rate)
clk_set_rate(parent->clk, new_rate);
}
static int voter_clk_is_enabled(struct clk_hw *hw)
{
struct clk_voter *v = to_clk_voter(hw);
return v->enabled;
}
static long voter_clk_round_rate(struct clk_hw *hw, unsigned long rate,
unsigned long *parent_rate)
{
struct clk_hw *parent_hw = clk_hw_get_parent(hw);
if (!parent_hw)
return -EINVAL;
return clk_hw_round_rate(parent_hw, rate);
}
static unsigned long voter_clk_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
struct clk_voter *v = to_clk_voter(hw);
return v->rate;
}
const struct clk_ops clk_ops_voter = {
.prepare = voter_clk_prepare,
.unprepare = voter_clk_unprepare,
.set_rate = voter_clk_set_rate,
.is_enabled = voter_clk_is_enabled,
.round_rate = voter_clk_round_rate,
.recalc_rate = voter_clk_recalc_rate,
};