/* * Copyright (c) 2013, The Linux Foundation. All rights reserved. * * This software is licensed under the terms of the GNU General Public * License version 2, as published by the Free Software Foundation, and * may be copied, distributed, and modified under those terms. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include #include #include #include #include #include #include #include "clk-branch.h" static bool clk_branch_in_hwcg_mode(const struct clk_branch *br) { u32 val; if (!br->hwcg_reg) return 0; regmap_read(br->clkr.regmap, br->hwcg_reg, &val); return !!(val & BIT(br->hwcg_bit)); } static bool clk_branch_check_halt(const struct clk_branch *br, bool enabling) { bool invert = (br->halt_check == BRANCH_HALT_ENABLE); u32 val; regmap_read(br->clkr.regmap, br->halt_reg, &val); val &= BIT(br->halt_bit); if (invert) val = !val; return !!val == !enabling; } #define BRANCH_CLK_OFF BIT(31) #define BRANCH_NOC_FSM_STATUS_SHIFT 28 #define BRANCH_NOC_FSM_STATUS_MASK 0x7 #define BRANCH_NOC_FSM_STATUS_ON (0x2 << BRANCH_NOC_FSM_STATUS_SHIFT) static bool clk_branch2_check_halt(const struct clk_branch *br, bool enabling) { u32 val; u32 mask; mask = BRANCH_NOC_FSM_STATUS_MASK << BRANCH_NOC_FSM_STATUS_SHIFT; mask |= BRANCH_CLK_OFF; regmap_read(br->clkr.regmap, br->halt_reg, &val); if (enabling) { val &= mask; return (val & BRANCH_CLK_OFF) == 0 || val == BRANCH_NOC_FSM_STATUS_ON; } else { return val & BRANCH_CLK_OFF; } } static int clk_branch_wait(const struct clk_branch *br, bool enabling, bool (check_halt)(const struct clk_branch *, bool)) { bool voted = br->halt_check & BRANCH_VOTED; const char *name = __clk_get_name(br->clkr.hw.clk); /* Skip checking halt bit if the clock is in hardware gated mode */ if (clk_branch_in_hwcg_mode(br)) return 0; if (br->halt_check == BRANCH_HALT_DELAY || (!enabling && voted)) { udelay(10); } else if (br->halt_check == BRANCH_HALT_ENABLE || br->halt_check == BRANCH_HALT || (enabling && voted)) { int count = 200; while (count-- > 0) { if (check_halt(br, enabling)) return 0; udelay(1); } WARN(1, "%s status stuck at 'o%s'", name, enabling ? "ff" : "n"); return -EBUSY; } return 0; } static int clk_branch_toggle(struct clk_hw *hw, bool en, bool (check_halt)(const struct clk_branch *, bool)) { struct clk_branch *br = to_clk_branch(hw); int ret; if (en) { ret = clk_enable_regmap(hw); if (ret) return ret; } else { clk_disable_regmap(hw); } return clk_branch_wait(br, en, check_halt); } static int clk_branch_enable(struct clk_hw *hw) { return clk_branch_toggle(hw, true, clk_branch_check_halt); } static void clk_branch_disable(struct clk_hw *hw) { clk_branch_toggle(hw, false, clk_branch_check_halt); } const struct clk_ops clk_branch_ops = { .enable = clk_branch_enable, .disable = clk_branch_disable, .is_enabled = clk_is_enabled_regmap, }; EXPORT_SYMBOL_GPL(clk_branch_ops); static int clk_branch2_enable(struct clk_hw *hw) { return clk_branch_toggle(hw, true, clk_branch2_check_halt); } static void clk_branch2_disable(struct clk_hw *hw) { clk_branch_toggle(hw, false, clk_branch2_check_halt); } const struct clk_ops clk_branch2_ops = { .enable = clk_branch2_enable, .disable = clk_branch2_disable, .is_enabled = clk_is_enabled_regmap, }; EXPORT_SYMBOL_GPL(clk_branch2_ops); const struct clk_ops clk_branch_simple_ops = { .enable = clk_enable_regmap, .disable = clk_disable_regmap, .is_enabled = clk_is_enabled_regmap, }; EXPORT_SYMBOL_GPL(clk_branch_simple_ops);