diff options
Diffstat (limited to 'drivers/clk/qcom/clk-branch.c')
-rw-r--r-- | drivers/clk/qcom/clk-branch.c | 155 |
1 files changed, 155 insertions, 0 deletions
diff --git a/drivers/clk/qcom/clk-branch.c b/drivers/clk/qcom/clk-branch.c new file mode 100644 index 0000000000..fc4735f74f --- /dev/null +++ b/drivers/clk/qcom/clk-branch.c @@ -0,0 +1,155 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2013, The Linux Foundation. All rights reserved. + */ + +#include <linux/kernel.h> +#include <linux/bitops.h> +#include <linux/err.h> +#include <linux/delay.h> +#include <linux/export.h> +#include <linux/clk-provider.h> +#include <linux/regmap.h> + +#include "clk-branch.h" + +static bool clk_branch_in_hwcg_mode(const struct clk_branch *br) +{ + u32 val; + + if (!br->hwcg_reg) + return false; + + 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; +} + +static bool clk_branch2_check_halt(const struct clk_branch *br, bool enabling) +{ + u32 val; + u32 mask; + bool invert = (br->halt_check == BRANCH_HALT_ENABLE); + + mask = CBCR_NOC_FSM_STATUS; + mask |= CBCR_CLK_OFF; + + regmap_read(br->clkr.regmap, br->halt_reg, &val); + + if (enabling) { + val &= mask; + return (val & CBCR_CLK_OFF) == (invert ? CBCR_CLK_OFF : 0) || + FIELD_GET(CBCR_NOC_FSM_STATUS, val) == FSM_STATUS_ON; + } + return (val & CBCR_CLK_OFF) == (invert ? 0 : CBCR_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 char *name = clk_hw_get_name(&br->clkr.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 = 200; + + while (count-- > 0) { + if (check_halt(br, enabling)) + return 0; + udelay(1); + } + WARN(1, "%s status stuck at 'o%s'", name, + 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); + } + + 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 void clk_branch_disable(struct clk_hw *hw) +{ + clk_branch_toggle(hw, false, clk_branch_check_halt); +} + +const struct clk_ops clk_branch_ops = { + .enable = clk_branch_enable, + .disable = clk_branch_disable, + .is_enabled = clk_is_enabled_regmap, +}; +EXPORT_SYMBOL_GPL(clk_branch_ops); + +static int clk_branch2_enable(struct clk_hw *hw) +{ + return clk_branch_toggle(hw, true, clk_branch2_check_halt); +} + +static void clk_branch2_disable(struct clk_hw *hw) +{ + clk_branch_toggle(hw, false, clk_branch2_check_halt); +} + +const struct clk_ops clk_branch2_ops = { + .enable = clk_branch2_enable, + .disable = clk_branch2_disable, + .is_enabled = clk_is_enabled_regmap, +}; +EXPORT_SYMBOL_GPL(clk_branch2_ops); + +const struct clk_ops clk_branch2_aon_ops = { + .enable = clk_branch2_enable, + .is_enabled = clk_is_enabled_regmap, +}; +EXPORT_SYMBOL_GPL(clk_branch2_aon_ops); + +const struct clk_ops clk_branch_simple_ops = { + .enable = clk_enable_regmap, + .disable = clk_disable_regmap, + .is_enabled = clk_is_enabled_regmap, +}; +EXPORT_SYMBOL_GPL(clk_branch_simple_ops); |