1// SPDX-License-Identifier: GPL-2.0
2/*
3 * Copyright (c) 2013, The Linux Foundation. All rights reserved.
4 */
5
6#include <linux/kernel.h>
7#include <linux/bitops.h>
8#include <linux/err.h>
9#include <linux/delay.h>
10#include <linux/export.h>
11#include <linux/clk-provider.h>
12#include <linux/regmap.h>
13
14#include "clk-branch.h"
15
16static bool clk_branch_in_hwcg_mode(const struct clk_branch *br)
17{
18	u32 val;
19
20	if (!br->hwcg_reg)
21		return false;
22
23	regmap_read(br->clkr.regmap, br->hwcg_reg, &val);
24
25	return !!(val & BIT(br->hwcg_bit));
26}
27
28static bool clk_branch_check_halt(const struct clk_branch *br, bool enabling)
29{
30	bool invert = (br->halt_check == BRANCH_HALT_ENABLE);
31	u32 val;
32
33	regmap_read(br->clkr.regmap, br->halt_reg, &val);
34
35	val &= BIT(br->halt_bit);
36	if (invert)
37		val = !val;
38
39	return !!val == !enabling;
40}
41
42#define BRANCH_CLK_OFF			BIT(31)
43#define BRANCH_NOC_FSM_STATUS_SHIFT	28
44#define BRANCH_NOC_FSM_STATUS_MASK	0x7
45#define BRANCH_NOC_FSM_STATUS_ON	(0x2 << BRANCH_NOC_FSM_STATUS_SHIFT)
46
47static bool clk_branch2_check_halt(const struct clk_branch *br, bool enabling)
48{
49	u32 val;
50	u32 mask;
51
52	mask = BRANCH_NOC_FSM_STATUS_MASK << BRANCH_NOC_FSM_STATUS_SHIFT;
53	mask |= BRANCH_CLK_OFF;
54
55	regmap_read(br->clkr.regmap, br->halt_reg, &val);
56
57	if (enabling) {
58		val &= mask;
59		return (val & BRANCH_CLK_OFF) == 0 ||
60			val == BRANCH_NOC_FSM_STATUS_ON;
61	} else {
62		return val & BRANCH_CLK_OFF;
63	}
64}
65
66static int clk_branch_wait(const struct clk_branch *br, bool enabling,
67		bool (check_halt)(const struct clk_branch *, bool))
68{
69	bool voted = br->halt_check & BRANCH_VOTED;
70	const char *name = clk_hw_get_name(&br->clkr.hw);
71
72	/*
73	 * Skip checking halt bit if we're explicitly ignoring the bit or the
74	 * clock is in hardware gated mode
75	 */
76	if (br->halt_check == BRANCH_HALT_SKIP || clk_branch_in_hwcg_mode(br))
77		return 0;
78
79	if (br->halt_check == BRANCH_HALT_DELAY || (!enabling && voted)) {
80		udelay(10);
81	} else if (br->halt_check == BRANCH_HALT_ENABLE ||
82		   br->halt_check == BRANCH_HALT ||
83		   (enabling && voted)) {
84		int count = 200;
85
86		while (count-- > 0) {
87			if (check_halt(br, enabling))
88				return 0;
89			udelay(1);
90		}
91		WARN(1, "%s status stuck at 'o%s'", name,
92				enabling ? "ff" : "n");
93		return -EBUSY;
94	}
95	return 0;
96}
97
98static int clk_branch_toggle(struct clk_hw *hw, bool en,
99		bool (check_halt)(const struct clk_branch *, bool))
100{
101	struct clk_branch *br = to_clk_branch(hw);
102	int ret;
103
104	if (en) {
105		ret = clk_enable_regmap(hw);
106		if (ret)
107			return ret;
108	} else {
109		clk_disable_regmap(hw);
110	}
111
112	return clk_branch_wait(br, en, check_halt);
113}
114
115static int clk_branch_enable(struct clk_hw *hw)
116{
117	return clk_branch_toggle(hw, true, clk_branch_check_halt);
118}
119
120static void clk_branch_disable(struct clk_hw *hw)
121{
122	clk_branch_toggle(hw, false, clk_branch_check_halt);
123}
124
125const struct clk_ops clk_branch_ops = {
126	.enable = clk_branch_enable,
127	.disable = clk_branch_disable,
128	.is_enabled = clk_is_enabled_regmap,
129};
130EXPORT_SYMBOL_GPL(clk_branch_ops);
131
132static int clk_branch2_enable(struct clk_hw *hw)
133{
134	return clk_branch_toggle(hw, true, clk_branch2_check_halt);
135}
136
137static void clk_branch2_disable(struct clk_hw *hw)
138{
139	clk_branch_toggle(hw, false, clk_branch2_check_halt);
140}
141
142const struct clk_ops clk_branch2_ops = {
143	.enable = clk_branch2_enable,
144	.disable = clk_branch2_disable,
145	.is_enabled = clk_is_enabled_regmap,
146};
147EXPORT_SYMBOL_GPL(clk_branch2_ops);
148
149const struct clk_ops clk_branch2_aon_ops = {
150	.enable = clk_branch2_enable,
151	.is_enabled = clk_is_enabled_regmap,
152};
153EXPORT_SYMBOL_GPL(clk_branch2_aon_ops);
154
155const struct clk_ops clk_branch_simple_ops = {
156	.enable = clk_enable_regmap,
157	.disable = clk_disable_regmap,
158	.is_enabled = clk_is_enabled_regmap,
159};
160EXPORT_SYMBOL_GPL(clk_branch_simple_ops);
161