162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright (C) 2010-2011 Canonical Ltd <jeremy.kerr@canonical.com>
462306a36Sopenharmony_ci * Copyright (C) 2011-2012 Mike Turquette, Linaro Ltd <mturquette@linaro.org>
562306a36Sopenharmony_ci *
662306a36Sopenharmony_ci * Gated clock implementation
762306a36Sopenharmony_ci */
862306a36Sopenharmony_ci
962306a36Sopenharmony_ci#include <linux/clk-provider.h>
1062306a36Sopenharmony_ci#include <linux/export.h>
1162306a36Sopenharmony_ci#include <linux/module.h>
1262306a36Sopenharmony_ci#include <linux/slab.h>
1362306a36Sopenharmony_ci#include <linux/io.h>
1462306a36Sopenharmony_ci#include <linux/err.h>
1562306a36Sopenharmony_ci#include <linux/string.h>
1662306a36Sopenharmony_ci#include "clk.h"
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_ci/**
1962306a36Sopenharmony_ci * DOC: basic gateable clock which can gate and ungate its output
2062306a36Sopenharmony_ci *
2162306a36Sopenharmony_ci * Traits of this clock:
2262306a36Sopenharmony_ci * prepare - clk_(un)prepare only ensures parent is (un)prepared
2362306a36Sopenharmony_ci * enable - clk_enable and clk_disable are functional & control gating
2462306a36Sopenharmony_ci * rate - inherits rate from parent.  No clk_set_rate support
2562306a36Sopenharmony_ci * parent - fixed parent.  No clk_set_parent support
2662306a36Sopenharmony_ci */
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_cistruct clk_gate2 {
2962306a36Sopenharmony_ci	struct clk_hw hw;
3062306a36Sopenharmony_ci	void __iomem	*reg;
3162306a36Sopenharmony_ci	u8		bit_idx;
3262306a36Sopenharmony_ci	u8		cgr_val;
3362306a36Sopenharmony_ci	u8		cgr_mask;
3462306a36Sopenharmony_ci	u8		flags;
3562306a36Sopenharmony_ci	spinlock_t	*lock;
3662306a36Sopenharmony_ci	unsigned int	*share_count;
3762306a36Sopenharmony_ci};
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_ci#define to_clk_gate2(_hw) container_of(_hw, struct clk_gate2, hw)
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_cistatic void clk_gate2_do_shared_clks(struct clk_hw *hw, bool enable)
4262306a36Sopenharmony_ci{
4362306a36Sopenharmony_ci	struct clk_gate2 *gate = to_clk_gate2(hw);
4462306a36Sopenharmony_ci	u32 reg;
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_ci	reg = readl(gate->reg);
4762306a36Sopenharmony_ci	reg &= ~(gate->cgr_mask << gate->bit_idx);
4862306a36Sopenharmony_ci	if (enable)
4962306a36Sopenharmony_ci		reg |= (gate->cgr_val & gate->cgr_mask) << gate->bit_idx;
5062306a36Sopenharmony_ci	writel(reg, gate->reg);
5162306a36Sopenharmony_ci}
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_cistatic int clk_gate2_enable(struct clk_hw *hw)
5462306a36Sopenharmony_ci{
5562306a36Sopenharmony_ci	struct clk_gate2 *gate = to_clk_gate2(hw);
5662306a36Sopenharmony_ci	unsigned long flags;
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_ci	spin_lock_irqsave(gate->lock, flags);
5962306a36Sopenharmony_ci
6062306a36Sopenharmony_ci	if (gate->share_count && (*gate->share_count)++ > 0)
6162306a36Sopenharmony_ci		goto out;
6262306a36Sopenharmony_ci
6362306a36Sopenharmony_ci	clk_gate2_do_shared_clks(hw, true);
6462306a36Sopenharmony_ciout:
6562306a36Sopenharmony_ci	spin_unlock_irqrestore(gate->lock, flags);
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_ci	return 0;
6862306a36Sopenharmony_ci}
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_cistatic void clk_gate2_disable(struct clk_hw *hw)
7162306a36Sopenharmony_ci{
7262306a36Sopenharmony_ci	struct clk_gate2 *gate = to_clk_gate2(hw);
7362306a36Sopenharmony_ci	unsigned long flags;
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_ci	spin_lock_irqsave(gate->lock, flags);
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_ci	if (gate->share_count) {
7862306a36Sopenharmony_ci		if (WARN_ON(*gate->share_count == 0))
7962306a36Sopenharmony_ci			goto out;
8062306a36Sopenharmony_ci		else if (--(*gate->share_count) > 0)
8162306a36Sopenharmony_ci			goto out;
8262306a36Sopenharmony_ci	}
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_ci	clk_gate2_do_shared_clks(hw, false);
8562306a36Sopenharmony_ciout:
8662306a36Sopenharmony_ci	spin_unlock_irqrestore(gate->lock, flags);
8762306a36Sopenharmony_ci}
8862306a36Sopenharmony_ci
8962306a36Sopenharmony_cistatic int clk_gate2_reg_is_enabled(void __iomem *reg, u8 bit_idx,
9062306a36Sopenharmony_ci					u8 cgr_val, u8 cgr_mask)
9162306a36Sopenharmony_ci{
9262306a36Sopenharmony_ci	u32 val = readl(reg);
9362306a36Sopenharmony_ci
9462306a36Sopenharmony_ci	if (((val >> bit_idx) & cgr_mask) == cgr_val)
9562306a36Sopenharmony_ci		return 1;
9662306a36Sopenharmony_ci
9762306a36Sopenharmony_ci	return 0;
9862306a36Sopenharmony_ci}
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_cistatic int clk_gate2_is_enabled(struct clk_hw *hw)
10162306a36Sopenharmony_ci{
10262306a36Sopenharmony_ci	struct clk_gate2 *gate = to_clk_gate2(hw);
10362306a36Sopenharmony_ci	unsigned long flags;
10462306a36Sopenharmony_ci	int ret = 0;
10562306a36Sopenharmony_ci
10662306a36Sopenharmony_ci	spin_lock_irqsave(gate->lock, flags);
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_ci	ret = clk_gate2_reg_is_enabled(gate->reg, gate->bit_idx,
10962306a36Sopenharmony_ci					gate->cgr_val, gate->cgr_mask);
11062306a36Sopenharmony_ci
11162306a36Sopenharmony_ci	spin_unlock_irqrestore(gate->lock, flags);
11262306a36Sopenharmony_ci
11362306a36Sopenharmony_ci	return ret;
11462306a36Sopenharmony_ci}
11562306a36Sopenharmony_ci
11662306a36Sopenharmony_cistatic void clk_gate2_disable_unused(struct clk_hw *hw)
11762306a36Sopenharmony_ci{
11862306a36Sopenharmony_ci	struct clk_gate2 *gate = to_clk_gate2(hw);
11962306a36Sopenharmony_ci	unsigned long flags;
12062306a36Sopenharmony_ci
12162306a36Sopenharmony_ci	spin_lock_irqsave(gate->lock, flags);
12262306a36Sopenharmony_ci
12362306a36Sopenharmony_ci	if (!gate->share_count || *gate->share_count == 0)
12462306a36Sopenharmony_ci		clk_gate2_do_shared_clks(hw, false);
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_ci	spin_unlock_irqrestore(gate->lock, flags);
12762306a36Sopenharmony_ci}
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_cistatic const struct clk_ops clk_gate2_ops = {
13062306a36Sopenharmony_ci	.enable = clk_gate2_enable,
13162306a36Sopenharmony_ci	.disable = clk_gate2_disable,
13262306a36Sopenharmony_ci	.disable_unused = clk_gate2_disable_unused,
13362306a36Sopenharmony_ci	.is_enabled = clk_gate2_is_enabled,
13462306a36Sopenharmony_ci};
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_cistruct clk_hw *clk_hw_register_gate2(struct device *dev, const char *name,
13762306a36Sopenharmony_ci		const char *parent_name, unsigned long flags,
13862306a36Sopenharmony_ci		void __iomem *reg, u8 bit_idx, u8 cgr_val, u8 cgr_mask,
13962306a36Sopenharmony_ci		u8 clk_gate2_flags, spinlock_t *lock,
14062306a36Sopenharmony_ci		unsigned int *share_count)
14162306a36Sopenharmony_ci{
14262306a36Sopenharmony_ci	struct clk_gate2 *gate;
14362306a36Sopenharmony_ci	struct clk_hw *hw;
14462306a36Sopenharmony_ci	struct clk_init_data init;
14562306a36Sopenharmony_ci	int ret;
14662306a36Sopenharmony_ci
14762306a36Sopenharmony_ci	gate = kzalloc(sizeof(struct clk_gate2), GFP_KERNEL);
14862306a36Sopenharmony_ci	if (!gate)
14962306a36Sopenharmony_ci		return ERR_PTR(-ENOMEM);
15062306a36Sopenharmony_ci
15162306a36Sopenharmony_ci	/* struct clk_gate2 assignments */
15262306a36Sopenharmony_ci	gate->reg = reg;
15362306a36Sopenharmony_ci	gate->bit_idx = bit_idx;
15462306a36Sopenharmony_ci	gate->cgr_val = cgr_val;
15562306a36Sopenharmony_ci	gate->cgr_mask = cgr_mask;
15662306a36Sopenharmony_ci	gate->flags = clk_gate2_flags;
15762306a36Sopenharmony_ci	gate->lock = lock;
15862306a36Sopenharmony_ci	gate->share_count = share_count;
15962306a36Sopenharmony_ci
16062306a36Sopenharmony_ci	init.name = name;
16162306a36Sopenharmony_ci	init.ops = &clk_gate2_ops;
16262306a36Sopenharmony_ci	init.flags = flags;
16362306a36Sopenharmony_ci	init.parent_names = parent_name ? &parent_name : NULL;
16462306a36Sopenharmony_ci	init.num_parents = parent_name ? 1 : 0;
16562306a36Sopenharmony_ci
16662306a36Sopenharmony_ci	gate->hw.init = &init;
16762306a36Sopenharmony_ci	hw = &gate->hw;
16862306a36Sopenharmony_ci
16962306a36Sopenharmony_ci	ret = clk_hw_register(dev, hw);
17062306a36Sopenharmony_ci	if (ret) {
17162306a36Sopenharmony_ci		kfree(gate);
17262306a36Sopenharmony_ci		return ERR_PTR(ret);
17362306a36Sopenharmony_ci	}
17462306a36Sopenharmony_ci
17562306a36Sopenharmony_ci	return hw;
17662306a36Sopenharmony_ci}
17762306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(clk_hw_register_gate2);
178