You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
154 lines
3.3 KiB
154 lines
3.3 KiB
/*
|
|
* Copyright (c) 2017-2018, 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.
|
|
*/
|
|
|
|
#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;
|
|
}
|
|
|
|
int voter_clk_handoff(struct clk_hw *hw)
|
|
{
|
|
struct clk_voter *v = to_clk_voter(hw);
|
|
|
|
v->enabled = true;
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(voter_clk_handoff);
|
|
|
|
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,
|
|
};
|
|
|