linux/drivers/clk/qcom/clk-branch.c
<<
>>
Prefs
   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 0;
  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_branch_simple_ops = {
 150        .enable = clk_enable_regmap,
 151        .disable = clk_disable_regmap,
 152        .is_enabled = clk_is_enabled_regmap,
 153};
 154EXPORT_SYMBOL_GPL(clk_branch_simple_ops);
 155