diff options
Diffstat (limited to 'drivers/clk/qcom/clk-branch.c')
| -rw-r--r-- | drivers/clk/qcom/clk-branch.c | 247 |
1 files changed, 243 insertions, 4 deletions
diff --git a/drivers/clk/qcom/clk-branch.c b/drivers/clk/qcom/clk-branch.c index 26f7af315066..3e9cd9909b86 100644 --- a/drivers/clk/qcom/clk-branch.c +++ b/drivers/clk/qcom/clk-branch.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, The Linux Foundation. All rights reserved. + * Copyright (c) 2013, 2016-2017, 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 @@ -16,10 +16,14 @@ #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 "clk-branch.h" +#include "clk-debug.h" +#include "clk-regmap.h" +#include "common.h" static bool clk_branch_in_hwcg_mode(const struct clk_branch *br) { @@ -75,12 +79,22 @@ 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 char *name = clk_hw_get_name(&br->clkr.hw); + const struct clk_hw *hw = &br->clkr.hw; + const char *name = clk_hw_get_name(hw); /* Skip checking halt bit if the clock is in hardware gated mode */ if (clk_branch_in_hwcg_mode(br)) return 0; + /* + * Some of the BRANCH_VOTED clocks could be controlled by other + * masters via voting registers, and would require to add delay + * polling for the status bit to allow previous clk_disable + * by the GDS controller to go through. + */ + if (enabling && voted) + udelay(5); + if (br->halt_check == BRANCH_HALT_DELAY || (!enabling && voted)) { udelay(10); } else if (br->halt_check == BRANCH_HALT_ENABLE || @@ -93,8 +107,10 @@ static int clk_branch_wait(const struct clk_branch *br, bool enabling, return 0; udelay(1); } - WARN(1, "%s status stuck at 'o%s'", name, - enabling ? "ff" : "n"); + + WARN_CLK(hw->core, name, 1, "status stuck at 'o%s'", + enabling ? "ff" : "n"); + return -EBUSY; } return 0; @@ -122,18 +138,103 @@ 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; + + regmap_read(regmap, reg, &cbcr_val); + + switch (flags) { + case CLKFLAG_PERIPH_OFF_SET: + cbcr_val |= BIT(12); + break; + case CLKFLAG_PERIPH_OFF_CLEAR: + cbcr_val &= ~BIT(12); + break; + case CLKFLAG_RETAIN_PERIPH: + cbcr_val |= BIT(13); + break; + case CLKFLAG_NORETAIN_PERIPH: + cbcr_val &= ~BIT(13); + break; + case CLKFLAG_RETAIN_MEM: + cbcr_val |= BIT(14); + break; + case CLKFLAG_NORETAIN_MEM: + cbcr_val &= ~BIT(14); + break; + default: + return -EINVAL; + } + + regmap_write(regmap, reg, cbcr_val); + + /* 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 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, }; EXPORT_SYMBOL_GPL(clk_branch_ops); +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) && + !(br->halt_check & BRANCH_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); + } + } + } +} + static int clk_branch2_enable(struct clk_hw *hw) { return clk_branch_toggle(hw, true, clk_branch2_check_halt); @@ -148,9 +249,147 @@ const struct clk_ops clk_branch2_ops = { .enable = clk_branch2_enable, .disable = clk_branch2_disable, .is_enabled = clk_is_enabled_regmap, + .set_flags = clk_branch_set_flags, + .list_registers = clk_branch2_list_registers, + .debug_init = clk_debug_measure_add, }; 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) +{ + /* + * Make sure the branch clock has CLK_SET_RATE_PARENT flag, + * and the RCG has FORCE_ENABLE_RCGR flag set. + */ + if (!(hw->init->flags & CLK_SET_RATE_PARENT)) { + pr_err("set rate would not get propagated to parent\n"); + 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_get_hw(clk_get_parent(hw->clk)); + 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_get_hw(clk_get_parent(hw->clk)); + + /* 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_get_hw(clk_get_parent(hw->clk)); + + 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, + .list_registers = clk_branch2_list_registers, +}; +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); + } +} + +static int clk_gate2_set_flags(struct clk_hw *hw, unsigned flags) +{ + struct clk_gate2 *gt = to_clk_gate2(hw); + + return clk_cbcr_set_flags(gt->clkr.regmap, gt->clkr.enable_reg, + flags); +} + +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, + .set_flags = clk_gate2_set_flags, + .debug_init = clk_debug_measure_add, +}; +EXPORT_SYMBOL_GPL(clk_gate2_ops); + const struct clk_ops clk_branch_simple_ops = { .enable = clk_enable_regmap, .disable = clk_disable_regmap, |
