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 false; 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 static bool clk_branch2_check_halt(const struct clk_branch *br, bool enabling) 43 { 44 u32 val; 45 u32 mask; 46 47 mask = CBCR_NOC_FSM_STATUS; 48 mask |= CBCR_CLK_OFF; 49 50 regmap_read(br->clkr.regmap, br->halt_reg, &val); 51 52 if (enabling) { 53 val &= mask; 54 return (val & CBCR_CLK_OFF) == 0 || 55 FIELD_GET(CBCR_NOC_FSM_STATUS, val) == FSM_STATUS_ON; 56 } else { 57 return val & CBCR_CLK_OFF; 58 } 59 } 60 61 static int clk_branch_wait(const struct clk_branch *br, bool enabling, 62 bool (check_halt)(const struct clk_branch *, bool)) 63 { 64 bool voted = br->halt_check & BRANCH_VOTED; 65 const char *name = clk_hw_get_name(&br->clkr.hw); 66 67 /* 68 * Skip checking halt bit if we're explicitly ignoring the bit or the 69 * clock is in hardware gated mode 70 */ 71 if (br->halt_check == BRANCH_HALT_SKIP || clk_branch_in_hwcg_mode(br)) 72 return 0; 73 74 if (br->halt_check == BRANCH_HALT_DELAY || (!enabling && voted)) { 75 udelay(10); 76 } else if (br->halt_check == BRANCH_HALT_ENABLE || 77 br->halt_check == BRANCH_HALT || 78 (enabling && voted)) { 79 int count = 200; 80 81 while (count-- > 0) { 82 if (check_halt(br, enabling)) 83 return 0; 84 udelay(1); 85 } 86 WARN(1, "%s status stuck at 'o%s'", name, 87 enabling ? "ff" : "n"); 88 return -EBUSY; 89 } 90 return 0; 91 } 92 93 static int clk_branch_toggle(struct clk_hw *hw, bool en, 94 bool (check_halt)(const struct clk_branch *, bool)) 95 { 96 struct clk_branch *br = to_clk_branch(hw); 97 int ret; 98 99 if (en) { 100 ret = clk_enable_regmap(hw); 101 if (ret) 102 return ret; 103 } else { 104 clk_disable_regmap(hw); 105 } 106 107 return clk_branch_wait(br, en, check_halt); 108 } 109 110 static int clk_branch_enable(struct clk_hw *hw) 111 { 112 return clk_branch_toggle(hw, true, clk_branch_check_halt); 113 } 114 115 static void clk_branch_disable(struct clk_hw *hw) 116 { 117 clk_branch_toggle(hw, false, clk_branch_check_halt); 118 } 119 120 const struct clk_ops clk_branch_ops = { 121 .enable = clk_branch_enable, 122 .disable = clk_branch_disable, 123 .is_enabled = clk_is_enabled_regmap, 124 }; 125 EXPORT_SYMBOL_GPL(clk_branch_ops); 126 127 static int clk_branch2_enable(struct clk_hw *hw) 128 { 129 return clk_branch_toggle(hw, true, clk_branch2_check_halt); 130 } 131 132 static void clk_branch2_disable(struct clk_hw *hw) 133 { 134 clk_branch_toggle(hw, false, clk_branch2_check_halt); 135 } 136 137 const struct clk_ops clk_branch2_ops = { 138 .enable = clk_branch2_enable, 139 .disable = clk_branch2_disable, 140 .is_enabled = clk_is_enabled_regmap, 141 }; 142 EXPORT_SYMBOL_GPL(clk_branch2_ops); 143 144 const struct clk_ops clk_branch2_aon_ops = { 145 .enable = clk_branch2_enable, 146 .is_enabled = clk_is_enabled_regmap, 147 }; 148 EXPORT_SYMBOL_GPL(clk_branch2_aon_ops); 149 150 const struct clk_ops clk_branch_simple_ops = { 151 .enable = clk_enable_regmap, 152 .disable = clk_disable_regmap, 153 .is_enabled = clk_is_enabled_regmap, 154 }; 155 EXPORT_SYMBOL_GPL(clk_branch_simple_ops); 156