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