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 16 static bool clk_branch_in_hwcg_mode(const struct clk_branch *br) 17 { 18 u32 val; 19 20 if (!br->hwcg_reg) 21 return 0; 22 23 regmap_read(br->clkr.regmap, br->hwcg_reg, &val); 24 25 return !!(val & BIT(br->hwcg_bit)); 26 } 27 28 static 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 47 static 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 66 static 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 98 static 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 115 static int clk_branch_enable(struct clk_hw *hw) 116 { 117 return clk_branch_toggle(hw, true, clk_branch_check_halt); 118 } 119 120 static void clk_branch_disable(struct clk_hw *hw) 121 { 122 clk_branch_toggle(hw, false, clk_branch_check_halt); 123 } 124 125 const struct clk_ops clk_branch_ops = { 126 .enable = clk_branch_enable, 127 .disable = clk_branch_disable, 128 .is_enabled = clk_is_enabled_regmap, 129 }; 130 EXPORT_SYMBOL_GPL(clk_branch_ops); 131 132 static int clk_branch2_enable(struct clk_hw *hw) 133 { 134 return clk_branch_toggle(hw, true, clk_branch2_check_halt); 135 } 136 137 static void clk_branch2_disable(struct clk_hw *hw) 138 { 139 clk_branch_toggle(hw, false, clk_branch2_check_halt); 140 } 141 142 const struct clk_ops clk_branch2_ops = { 143 .enable = clk_branch2_enable, 144 .disable = clk_branch2_disable, 145 .is_enabled = clk_is_enabled_regmap, 146 }; 147 EXPORT_SYMBOL_GPL(clk_branch2_ops); 148 149 const struct clk_ops clk_branch_simple_ops = { 150 .enable = clk_enable_regmap, 151 .disable = clk_disable_regmap, 152 .is_enabled = clk_is_enabled_regmap, 153 }; 154 EXPORT_SYMBOL_GPL(clk_branch_simple_ops); 155