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.
 
 
 
kernel_samsung_sm7125/drivers/clk/qcom/clk-branch.c

528 lines
12 KiB

/*
* Copyright (c) 2013, 2017-2018 The Linux Foundation. All rights reserved.
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* 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/kernel.h>
#include <linux/bitops.h>
#include <linux/err.h>
#include <linux/delay.h>
#include <linux/export.h>
#include <linux/clk.h>
#include <linux/clk-provider.h>
#include <linux/regmap.h>
#include <linux/clk/qcom.h>
#include "clk-branch.h"
#include "clk-regmap.h"
#include "common.h"
#include "clk-debug.h"
static bool clk_branch_in_hwcg_mode(const struct clk_branch *br)
{
u32 val;
if (!br->hwcg_reg)
return 0;
regmap_read(br->clkr.regmap, br->hwcg_reg, &val);
return !!(val & BIT(br->hwcg_bit));
}
static bool clk_branch_check_halt(const struct clk_branch *br, bool enabling)
{
bool invert = (br->halt_check == BRANCH_HALT_ENABLE);
u32 val;
regmap_read(br->clkr.regmap, br->halt_reg, &val);
val &= BIT(br->halt_bit);
if (invert)
val = !val;
return !!val == !enabling;
}
#define BRANCH_CLK_OFF BIT(31)
#define BRANCH_NOC_FSM_STATUS_SHIFT 28
#define BRANCH_NOC_FSM_STATUS_MASK 0x7
#define BRANCH_NOC_FSM_STATUS_ON (0x2 << BRANCH_NOC_FSM_STATUS_SHIFT)
static bool clk_branch2_check_halt(const struct clk_branch *br, bool enabling)
{
u32 val;
u32 mask;
mask = BRANCH_NOC_FSM_STATUS_MASK << BRANCH_NOC_FSM_STATUS_SHIFT;
mask |= BRANCH_CLK_OFF;
regmap_read(br->clkr.regmap, br->halt_reg, &val);
if (enabling) {
val &= mask;
return (val & BRANCH_CLK_OFF) == 0 ||
val == BRANCH_NOC_FSM_STATUS_ON;
} else {
return val & BRANCH_CLK_OFF;
}
}
static int clk_branch_wait(const struct clk_branch *br, bool enabling,
bool (check_halt)(const struct clk_branch *, bool))
{
bool voted = br->halt_check & BRANCH_VOTED;
const struct clk_hw *hw = &br->clkr.hw;
const char *name = clk_hw_get_name(hw);
/*
* Skip checking halt bit if we're explicitly ignoring the bit or the
* clock is in hardware gated mode
*/
if (br->halt_check == BRANCH_HALT_SKIP || clk_branch_in_hwcg_mode(br))
return 0;
if (br->halt_check == BRANCH_HALT_DELAY || (!enabling && voted)) {
udelay(10);
} else if (br->halt_check == BRANCH_HALT_ENABLE ||
br->halt_check == BRANCH_HALT ||
(enabling && voted)) {
int count = 500;
while (count-- > 0) {
if (check_halt(br, enabling))
return 0;
udelay(1);
}
WARN_CLK(hw->core, name, 1, "status stuck at 'o%s'",
enabling ? "ff" : "n");
return -EBUSY;
}
return 0;
}
static int clk_branch_toggle(struct clk_hw *hw, bool en,
bool (check_halt)(const struct clk_branch *, bool))
{
struct clk_branch *br = to_clk_branch(hw);
int ret;
if (en) {
ret = clk_enable_regmap(hw);
if (ret)
return ret;
} else {
clk_disable_regmap(hw);
}
/*
* Make sure enable/disable request goes through before waiting
* for CLK_OFF status to get updated.
*/
mb();
return clk_branch_wait(br, en, check_halt);
}
static int clk_branch_enable(struct clk_hw *hw)
{
return clk_branch_toggle(hw, true, clk_branch_check_halt);
}
static int clk_cbcr_set_flags(struct regmap *regmap, unsigned int reg,
unsigned long flags)
{
u32 cbcr_val = 0;
u32 cbcr_mask;
int ret;
switch (flags) {
case CLKFLAG_PERIPH_OFF_SET:
cbcr_val = cbcr_mask = BIT(12);
break;
case CLKFLAG_PERIPH_OFF_CLEAR:
cbcr_mask = BIT(12);
break;
case CLKFLAG_RETAIN_PERIPH:
cbcr_val = cbcr_mask = BIT(13);
break;
case CLKFLAG_NORETAIN_PERIPH:
cbcr_mask = BIT(13);
break;
case CLKFLAG_RETAIN_MEM:
cbcr_val = cbcr_mask = BIT(14);
break;
case CLKFLAG_NORETAIN_MEM:
cbcr_mask = BIT(14);
break;
default:
return -EINVAL;
}
ret = regmap_update_bits(regmap, reg, cbcr_mask, cbcr_val);
if (ret)
return ret;
/* Make sure power is enabled/disabled before returning. */
mb();
udelay(1);
return 0;
}
static void clk_branch_disable(struct clk_hw *hw)
{
clk_branch_toggle(hw, false, clk_branch_check_halt);
}
static int clk_branch_set_flags(struct clk_hw *hw, unsigned int flags)
{
struct clk_branch *br = to_clk_branch(hw);
return clk_cbcr_set_flags(br->clkr.regmap, br->halt_reg, flags);
}
const struct clk_ops clk_branch_ops = {
.enable = clk_branch_enable,
.disable = clk_branch_disable,
.is_enabled = clk_is_enabled_regmap,
.set_flags = clk_branch_set_flags,
.bus_vote = clk_debug_bus_vote,
};
EXPORT_SYMBOL_GPL(clk_branch_ops);
static int clk_branch2_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
struct clk_branch *branch = to_clk_branch(hw);
struct clk_hw *parent = clk_hw_get_parent(hw);
unsigned long curr_rate, new_rate, other_rate = 0;
int ret = 0;
if (!parent)
return -EPERM;
if (!branch->aggr_sibling_rates) {
branch->rate = parent_rate;
return 0;
}
other_rate = clk_aggregate_rate(hw, parent->core);
curr_rate = max(other_rate, branch->rate);
new_rate = max(other_rate, rate);
if (new_rate != curr_rate) {
ret = clk_set_rate(parent->clk, new_rate);
if (ret) {
pr_err("Failed to scale %s to %lu\n",
clk_hw_get_name(parent), new_rate);
goto err;
}
}
branch->rate = rate;
err:
return ret;
}
static long clk_branch2_round_rate(struct clk_hw *hw, unsigned long rate,
unsigned long *parent_rate)
{
struct clk_hw *parent = clk_hw_get_parent(hw);
unsigned long rrate = 0;
if (!parent)
return -EPERM;
rrate = clk_hw_round_rate(parent, rate);
/*
* If the rounded rate that's returned is valid, update the parent_rate
* field so that the set_rate() call can be propagated to the parent.
*/
if (rrate > 0)
*parent_rate = rrate;
else
pr_warn("Failed to get the parent's (%s) rounded rate\n",
clk_hw_get_name(parent));
return rrate;
}
static unsigned long clk_branch2_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
struct clk_branch *branch = to_clk_branch(hw);
if (!branch->aggr_sibling_rates)
return parent_rate;
return to_clk_branch(hw)->rate;
}
static int clk_branch2_enable(struct clk_hw *hw)
{
return clk_branch_toggle(hw, true, clk_branch2_check_halt);
}
static int clk_branch2_prepare(struct clk_hw *hw)
{
struct clk_branch *branch;
struct clk_hw *parent;
unsigned long curr_rate;
int ret = 0;
if (!hw)
return -EINVAL;
branch = to_clk_branch(hw);
parent = clk_hw_get_parent(hw);
if (!branch)
return -EINVAL;
/*
* Do the rate aggregation and scaling of the RCG in the prepare/
* unprepare functions to avoid potential RPM(/h) communication due to
* votes on the voltage rails.
*/
if (branch->aggr_sibling_rates) {
if (!parent)
return -EINVAL;
curr_rate = clk_aggregate_rate(hw, parent->core);
if (branch->rate > curr_rate) {
ret = clk_set_rate(parent->clk, branch->rate);
if (ret) {
pr_err("Failed to scale %s to %lu\n",
clk_hw_get_name(parent), branch->rate);
goto exit;
}
}
}
exit:
return ret;
}
static void clk_branch2_disable(struct clk_hw *hw)
{
clk_branch_toggle(hw, false, clk_branch2_check_halt);
}
static void clk_branch2_unprepare(struct clk_hw *hw)
{
struct clk_branch *branch;
struct clk_hw *parent;
unsigned long curr_rate, new_rate;
if (!hw)
return;
branch = to_clk_branch(hw);
parent = clk_hw_get_parent(hw);
if (!branch)
return;
if (branch->aggr_sibling_rates) {
if (!parent)
return;
new_rate = clk_aggregate_rate(hw, parent->core);
curr_rate = max(new_rate, branch->rate);
if (new_rate < curr_rate)
if (clk_set_rate(parent->clk, new_rate))
pr_err("Failed to scale %s to %lu\n",
clk_hw_get_name(parent), new_rate);
}
}
static void clk_branch2_list_registers(struct seq_file *f, struct clk_hw *hw)
{
struct clk_branch *br = to_clk_branch(hw);
struct clk_regmap *rclk = to_clk_regmap(hw);
int size, i, val;
static struct clk_register_data data[] = {
{"CBCR", 0x0},
};
static struct clk_register_data data1[] = {
{"APSS_VOTE", 0x0},
{"APSS_SLEEP_VOTE", 0x4},
};
size = ARRAY_SIZE(data);
for (i = 0; i < size; i++) {
regmap_read(br->clkr.regmap, br->halt_reg + data[i].offset,
&val);
clock_debug_output(f, false, "%20s: 0x%.8x\n",
data[i].name, val);
}
if (br->halt_check & BRANCH_HALT_VOTED) {
if (rclk->enable_reg) {
size = ARRAY_SIZE(data1);
for (i = 0; i < size; i++) {
regmap_read(br->clkr.regmap, rclk->enable_reg +
data1[i].offset, &val);
clock_debug_output(f, false, "%20s: 0x%.8x\n",
data1[i].name, val);
}
}
}
}
const struct clk_ops clk_branch2_ops = {
.prepare = clk_branch2_prepare,
.enable = clk_branch2_enable,
.unprepare = clk_branch2_unprepare,
.disable = clk_branch2_disable,
.is_enabled = clk_is_enabled_regmap,
.set_rate = clk_branch2_set_rate,
.round_rate = clk_branch2_round_rate,
.recalc_rate = clk_branch2_recalc_rate,
.set_flags = clk_branch_set_flags,
.list_registers = clk_branch2_list_registers,
.debug_init = clk_debug_measure_add,
.bus_vote = clk_debug_bus_vote,
};
EXPORT_SYMBOL_GPL(clk_branch2_ops);
static int clk_branch2_hw_ctl_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
if (!(hw->init->flags & CLK_SET_RATE_PARENT)) {
pr_err("SET_RATE_PARENT flag needs to be set for %s\n",
clk_hw_get_name(hw));
return -EINVAL;
}
return 0;
}
static unsigned long clk_branch2_hw_ctl_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
return parent_rate;
}
static int clk_branch2_hw_ctl_determine_rate(struct clk_hw *hw,
struct clk_rate_request *req)
{
struct clk_hw *clkp;
clkp = clk_hw_get_parent(hw);
if (!clkp)
return -EINVAL;
req->best_parent_hw = clkp;
req->best_parent_rate = clk_round_rate(clkp->clk, req->rate);
return 0;
}
static int clk_branch2_hw_ctl_enable(struct clk_hw *hw)
{
struct clk_hw *parent = clk_hw_get_parent(hw);
/* The parent branch clock should have been prepared prior to this. */
if (!parent || (parent && !clk_hw_is_prepared(parent)))
return -EINVAL;
return clk_enable_regmap(hw);
}
static void clk_branch2_hw_ctl_disable(struct clk_hw *hw)
{
struct clk_hw *parent = clk_hw_get_parent(hw);
if (!parent)
return;
clk_disable_regmap(hw);
}
const struct clk_ops clk_branch2_hw_ctl_ops = {
.enable = clk_branch2_hw_ctl_enable,
.disable = clk_branch2_hw_ctl_disable,
.is_enabled = clk_is_enabled_regmap,
.set_rate = clk_branch2_hw_ctl_set_rate,
.recalc_rate = clk_branch2_hw_ctl_recalc_rate,
.determine_rate = clk_branch2_hw_ctl_determine_rate,
.set_flags = clk_branch_set_flags,
.bus_vote = clk_debug_bus_vote,
};
EXPORT_SYMBOL_GPL(clk_branch2_hw_ctl_ops);
static int clk_gate_toggle(struct clk_hw *hw, bool en)
{
struct clk_gate2 *gt = to_clk_gate2(hw);
int ret = 0;
if (en) {
ret = clk_enable_regmap(hw);
if (ret)
return ret;
} else {
clk_disable_regmap(hw);
}
if (gt->udelay)
udelay(gt->udelay);
return ret;
}
static int clk_gate2_enable(struct clk_hw *hw)
{
return clk_gate_toggle(hw, true);
}
static void clk_gate2_disable(struct clk_hw *hw)
{
clk_gate_toggle(hw, false);
}
static void clk_gate2_list_registers(struct seq_file *f, struct clk_hw *hw)
{
struct clk_gate2 *gt = to_clk_gate2(hw);
int size, i, val;
static struct clk_register_data data[] = {
{"EN_REG", 0x0},
};
size = ARRAY_SIZE(data);
for (i = 0; i < size; i++) {
regmap_read(gt->clkr.regmap, gt->clkr.enable_reg +
data[i].offset, &val);
clock_debug_output(f, false, "%20s: 0x%.8x\n",
data[i].name, val);
}
}
const struct clk_ops clk_gate2_ops = {
.enable = clk_gate2_enable,
.disable = clk_gate2_disable,
.is_enabled = clk_is_enabled_regmap,
.list_registers = clk_gate2_list_registers,
.debug_init = clk_debug_measure_add,
.bus_vote = clk_debug_bus_vote,
};
EXPORT_SYMBOL_GPL(clk_gate2_ops);
const struct clk_ops clk_branch_simple_ops = {
.enable = clk_enable_regmap,
.disable = clk_disable_regmap,
.is_enabled = clk_is_enabled_regmap,
.bus_vote = clk_debug_bus_vote,
};
EXPORT_SYMBOL_GPL(clk_branch_simple_ops);