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 /* Skip checking halt bit if the clock is in hardware gated mode */ 81 if (clk_branch_in_hwcg_mode(br)) 82 return 0; 83 84 if (br->halt_check == BRANCH_HALT_DELAY || (!enabling && voted)) { 85 udelay(10); 86 } else if (br->halt_check == BRANCH_HALT_ENABLE || 87 br->halt_check == BRANCH_HALT || 88 (enabling && voted)) { 89 int count = 200; 90 91 while (count-- > 0) { 92 if (check_halt(br, enabling)) 93 return 0; 94 udelay(1); 95 } 96 WARN(1, "%s status stuck at 'o%s'", name, 97 enabling ? "ff" : "n"); 98 return -EBUSY; 99 } 100 return 0; 101 } 102 103 static int clk_branch_toggle(struct clk_hw *hw, bool en, 104 bool (check_halt)(const struct clk_branch *, bool)) 105 { 106 struct clk_branch *br = to_clk_branch(hw); 107 int ret; 108 109 if (en) { 110 ret = clk_enable_regmap(hw); 111 if (ret) 112 return ret; 113 } else { 114 clk_disable_regmap(hw); 115 } 116 117 return clk_branch_wait(br, en, check_halt); 118 } 119 120 static int clk_branch_enable(struct clk_hw *hw) 121 { 122 return clk_branch_toggle(hw, true, clk_branch_check_halt); 123 } 124 125 static void clk_branch_disable(struct clk_hw *hw) 126 { 127 clk_branch_toggle(hw, false, clk_branch_check_halt); 128 } 129 130 const struct clk_ops clk_branch_ops = { 131 .enable = clk_branch_enable, 132 .disable = clk_branch_disable, 133 .is_enabled = clk_is_enabled_regmap, 134 }; 135 EXPORT_SYMBOL_GPL(clk_branch_ops); 136 137 static int clk_branch2_enable(struct clk_hw *hw) 138 { 139 return clk_branch_toggle(hw, true, clk_branch2_check_halt); 140 } 141 142 static void clk_branch2_disable(struct clk_hw *hw) 143 { 144 clk_branch_toggle(hw, false, clk_branch2_check_halt); 145 } 146 147 const struct clk_ops clk_branch2_ops = { 148 .enable = clk_branch2_enable, 149 .disable = clk_branch2_disable, 150 .is_enabled = clk_is_enabled_regmap, 151 }; 152 EXPORT_SYMBOL_GPL(clk_branch2_ops); 153 154 const struct clk_ops clk_branch_simple_ops = { 155 .enable = clk_enable_regmap, 156 .disable = clk_disable_regmap, 157 .is_enabled = clk_is_enabled_regmap, 158 }; 159 EXPORT_SYMBOL_GPL(clk_branch_simple_ops); 160