18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Copyright (c) 2013, The Linux Foundation. All rights reserved.
48c2ecf20Sopenharmony_ci */
58c2ecf20Sopenharmony_ci
68c2ecf20Sopenharmony_ci#include <linux/kernel.h>
78c2ecf20Sopenharmony_ci#include <linux/bitops.h>
88c2ecf20Sopenharmony_ci#include <linux/err.h>
98c2ecf20Sopenharmony_ci#include <linux/delay.h>
108c2ecf20Sopenharmony_ci#include <linux/export.h>
118c2ecf20Sopenharmony_ci#include <linux/clk-provider.h>
128c2ecf20Sopenharmony_ci#include <linux/regmap.h>
138c2ecf20Sopenharmony_ci
148c2ecf20Sopenharmony_ci#include "clk-branch.h"
158c2ecf20Sopenharmony_ci
168c2ecf20Sopenharmony_cistatic bool clk_branch_in_hwcg_mode(const struct clk_branch *br)
178c2ecf20Sopenharmony_ci{
188c2ecf20Sopenharmony_ci	u32 val;
198c2ecf20Sopenharmony_ci
208c2ecf20Sopenharmony_ci	if (!br->hwcg_reg)
218c2ecf20Sopenharmony_ci		return false;
228c2ecf20Sopenharmony_ci
238c2ecf20Sopenharmony_ci	regmap_read(br->clkr.regmap, br->hwcg_reg, &val);
248c2ecf20Sopenharmony_ci
258c2ecf20Sopenharmony_ci	return !!(val & BIT(br->hwcg_bit));
268c2ecf20Sopenharmony_ci}
278c2ecf20Sopenharmony_ci
288c2ecf20Sopenharmony_cistatic bool clk_branch_check_halt(const struct clk_branch *br, bool enabling)
298c2ecf20Sopenharmony_ci{
308c2ecf20Sopenharmony_ci	bool invert = (br->halt_check == BRANCH_HALT_ENABLE);
318c2ecf20Sopenharmony_ci	u32 val;
328c2ecf20Sopenharmony_ci
338c2ecf20Sopenharmony_ci	regmap_read(br->clkr.regmap, br->halt_reg, &val);
348c2ecf20Sopenharmony_ci
358c2ecf20Sopenharmony_ci	val &= BIT(br->halt_bit);
368c2ecf20Sopenharmony_ci	if (invert)
378c2ecf20Sopenharmony_ci		val = !val;
388c2ecf20Sopenharmony_ci
398c2ecf20Sopenharmony_ci	return !!val == !enabling;
408c2ecf20Sopenharmony_ci}
418c2ecf20Sopenharmony_ci
428c2ecf20Sopenharmony_ci#define BRANCH_CLK_OFF			BIT(31)
438c2ecf20Sopenharmony_ci#define BRANCH_NOC_FSM_STATUS_SHIFT	28
448c2ecf20Sopenharmony_ci#define BRANCH_NOC_FSM_STATUS_MASK	0x7
458c2ecf20Sopenharmony_ci#define BRANCH_NOC_FSM_STATUS_ON	(0x2 << BRANCH_NOC_FSM_STATUS_SHIFT)
468c2ecf20Sopenharmony_ci
478c2ecf20Sopenharmony_cistatic bool clk_branch2_check_halt(const struct clk_branch *br, bool enabling)
488c2ecf20Sopenharmony_ci{
498c2ecf20Sopenharmony_ci	u32 val;
508c2ecf20Sopenharmony_ci	u32 mask;
518c2ecf20Sopenharmony_ci
528c2ecf20Sopenharmony_ci	mask = BRANCH_NOC_FSM_STATUS_MASK << BRANCH_NOC_FSM_STATUS_SHIFT;
538c2ecf20Sopenharmony_ci	mask |= BRANCH_CLK_OFF;
548c2ecf20Sopenharmony_ci
558c2ecf20Sopenharmony_ci	regmap_read(br->clkr.regmap, br->halt_reg, &val);
568c2ecf20Sopenharmony_ci
578c2ecf20Sopenharmony_ci	if (enabling) {
588c2ecf20Sopenharmony_ci		val &= mask;
598c2ecf20Sopenharmony_ci		return (val & BRANCH_CLK_OFF) == 0 ||
608c2ecf20Sopenharmony_ci			val == BRANCH_NOC_FSM_STATUS_ON;
618c2ecf20Sopenharmony_ci	} else {
628c2ecf20Sopenharmony_ci		return val & BRANCH_CLK_OFF;
638c2ecf20Sopenharmony_ci	}
648c2ecf20Sopenharmony_ci}
658c2ecf20Sopenharmony_ci
668c2ecf20Sopenharmony_cistatic int clk_branch_wait(const struct clk_branch *br, bool enabling,
678c2ecf20Sopenharmony_ci		bool (check_halt)(const struct clk_branch *, bool))
688c2ecf20Sopenharmony_ci{
698c2ecf20Sopenharmony_ci	bool voted = br->halt_check & BRANCH_VOTED;
708c2ecf20Sopenharmony_ci	const char *name = clk_hw_get_name(&br->clkr.hw);
718c2ecf20Sopenharmony_ci
728c2ecf20Sopenharmony_ci	/*
738c2ecf20Sopenharmony_ci	 * Skip checking halt bit if we're explicitly ignoring the bit or the
748c2ecf20Sopenharmony_ci	 * clock is in hardware gated mode
758c2ecf20Sopenharmony_ci	 */
768c2ecf20Sopenharmony_ci	if (br->halt_check == BRANCH_HALT_SKIP || clk_branch_in_hwcg_mode(br))
778c2ecf20Sopenharmony_ci		return 0;
788c2ecf20Sopenharmony_ci
798c2ecf20Sopenharmony_ci	if (br->halt_check == BRANCH_HALT_DELAY || (!enabling && voted)) {
808c2ecf20Sopenharmony_ci		udelay(10);
818c2ecf20Sopenharmony_ci	} else if (br->halt_check == BRANCH_HALT_ENABLE ||
828c2ecf20Sopenharmony_ci		   br->halt_check == BRANCH_HALT ||
838c2ecf20Sopenharmony_ci		   (enabling && voted)) {
848c2ecf20Sopenharmony_ci		int count = 200;
858c2ecf20Sopenharmony_ci
868c2ecf20Sopenharmony_ci		while (count-- > 0) {
878c2ecf20Sopenharmony_ci			if (check_halt(br, enabling))
888c2ecf20Sopenharmony_ci				return 0;
898c2ecf20Sopenharmony_ci			udelay(1);
908c2ecf20Sopenharmony_ci		}
918c2ecf20Sopenharmony_ci		WARN(1, "%s status stuck at 'o%s'", name,
928c2ecf20Sopenharmony_ci				enabling ? "ff" : "n");
938c2ecf20Sopenharmony_ci		return -EBUSY;
948c2ecf20Sopenharmony_ci	}
958c2ecf20Sopenharmony_ci	return 0;
968c2ecf20Sopenharmony_ci}
978c2ecf20Sopenharmony_ci
988c2ecf20Sopenharmony_cistatic int clk_branch_toggle(struct clk_hw *hw, bool en,
998c2ecf20Sopenharmony_ci		bool (check_halt)(const struct clk_branch *, bool))
1008c2ecf20Sopenharmony_ci{
1018c2ecf20Sopenharmony_ci	struct clk_branch *br = to_clk_branch(hw);
1028c2ecf20Sopenharmony_ci	int ret;
1038c2ecf20Sopenharmony_ci
1048c2ecf20Sopenharmony_ci	if (en) {
1058c2ecf20Sopenharmony_ci		ret = clk_enable_regmap(hw);
1068c2ecf20Sopenharmony_ci		if (ret)
1078c2ecf20Sopenharmony_ci			return ret;
1088c2ecf20Sopenharmony_ci	} else {
1098c2ecf20Sopenharmony_ci		clk_disable_regmap(hw);
1108c2ecf20Sopenharmony_ci	}
1118c2ecf20Sopenharmony_ci
1128c2ecf20Sopenharmony_ci	return clk_branch_wait(br, en, check_halt);
1138c2ecf20Sopenharmony_ci}
1148c2ecf20Sopenharmony_ci
1158c2ecf20Sopenharmony_cistatic int clk_branch_enable(struct clk_hw *hw)
1168c2ecf20Sopenharmony_ci{
1178c2ecf20Sopenharmony_ci	return clk_branch_toggle(hw, true, clk_branch_check_halt);
1188c2ecf20Sopenharmony_ci}
1198c2ecf20Sopenharmony_ci
1208c2ecf20Sopenharmony_cistatic void clk_branch_disable(struct clk_hw *hw)
1218c2ecf20Sopenharmony_ci{
1228c2ecf20Sopenharmony_ci	clk_branch_toggle(hw, false, clk_branch_check_halt);
1238c2ecf20Sopenharmony_ci}
1248c2ecf20Sopenharmony_ci
1258c2ecf20Sopenharmony_ciconst struct clk_ops clk_branch_ops = {
1268c2ecf20Sopenharmony_ci	.enable = clk_branch_enable,
1278c2ecf20Sopenharmony_ci	.disable = clk_branch_disable,
1288c2ecf20Sopenharmony_ci	.is_enabled = clk_is_enabled_regmap,
1298c2ecf20Sopenharmony_ci};
1308c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(clk_branch_ops);
1318c2ecf20Sopenharmony_ci
1328c2ecf20Sopenharmony_cistatic int clk_branch2_enable(struct clk_hw *hw)
1338c2ecf20Sopenharmony_ci{
1348c2ecf20Sopenharmony_ci	return clk_branch_toggle(hw, true, clk_branch2_check_halt);
1358c2ecf20Sopenharmony_ci}
1368c2ecf20Sopenharmony_ci
1378c2ecf20Sopenharmony_cistatic void clk_branch2_disable(struct clk_hw *hw)
1388c2ecf20Sopenharmony_ci{
1398c2ecf20Sopenharmony_ci	clk_branch_toggle(hw, false, clk_branch2_check_halt);
1408c2ecf20Sopenharmony_ci}
1418c2ecf20Sopenharmony_ci
1428c2ecf20Sopenharmony_ciconst struct clk_ops clk_branch2_ops = {
1438c2ecf20Sopenharmony_ci	.enable = clk_branch2_enable,
1448c2ecf20Sopenharmony_ci	.disable = clk_branch2_disable,
1458c2ecf20Sopenharmony_ci	.is_enabled = clk_is_enabled_regmap,
1468c2ecf20Sopenharmony_ci};
1478c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(clk_branch2_ops);
1488c2ecf20Sopenharmony_ci
1498c2ecf20Sopenharmony_ciconst struct clk_ops clk_branch2_aon_ops = {
1508c2ecf20Sopenharmony_ci	.enable = clk_branch2_enable,
1518c2ecf20Sopenharmony_ci	.is_enabled = clk_is_enabled_regmap,
1528c2ecf20Sopenharmony_ci};
1538c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(clk_branch2_aon_ops);
1548c2ecf20Sopenharmony_ci
1558c2ecf20Sopenharmony_ciconst struct clk_ops clk_branch_simple_ops = {
1568c2ecf20Sopenharmony_ci	.enable = clk_enable_regmap,
1578c2ecf20Sopenharmony_ci	.disable = clk_disable_regmap,
1588c2ecf20Sopenharmony_ci	.is_enabled = clk_is_enabled_regmap,
1598c2ecf20Sopenharmony_ci};
1608c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(clk_branch_simple_ops);
161