|  | /* | 
|  | * 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 <linux/kernel.h> | 
|  | #include <linux/bitops.h> | 
|  | #include <linux/err.h> | 
|  | #include <linux/delay.h> | 
|  | #include <linux/export.h> | 
|  | #include <linux/clk-provider.h> | 
|  | #include <linux/regmap.h> | 
|  |  | 
|  | #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); |