clk-branch.c (3720B)
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);