diff options
Diffstat (limited to 'drivers/clk/qcom/clk-branch.c')
| -rw-r--r-- | drivers/clk/qcom/clk-branch.c | 159 | 
1 files changed, 159 insertions, 0 deletions
diff --git a/drivers/clk/qcom/clk-branch.c b/drivers/clk/qcom/clk-branch.c new file mode 100644 index 00000000000..6b4d2bcb1a5 --- /dev/null +++ b/drivers/clk/qcom/clk-branch.c @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2013, 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-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 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 char *name = __clk_get_name(br->clkr.hw.clk); + +	/* Skip checking halt bit if the clock is in hardware gated mode */ +	if (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_branch_simple_ops = { +	.enable = clk_enable_regmap, +	.disable = clk_disable_regmap, +	.is_enabled = clk_is_enabled_regmap, +}; +EXPORT_SYMBOL_GPL(clk_branch_simple_ops);  | 
