162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright 2022 NXP
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Peng Fan <peng.fan@nxp.com>
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#include <linux/clk-provider.h>
962306a36Sopenharmony_ci#include <linux/errno.h>
1062306a36Sopenharmony_ci#include <linux/export.h>
1162306a36Sopenharmony_ci#include <linux/io.h>
1262306a36Sopenharmony_ci#include <linux/iopoll.h>
1362306a36Sopenharmony_ci#include <linux/slab.h>
1462306a36Sopenharmony_ci
1562306a36Sopenharmony_ci#include "clk.h"
1662306a36Sopenharmony_ci
1762306a36Sopenharmony_ci#define DIRECT_OFFSET		0x0
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_ci/*
2062306a36Sopenharmony_ci * 0b000 - LPCG will be OFF in any CPU mode.
2162306a36Sopenharmony_ci * 0b100 - LPCG will be ON in any CPU mode.
2262306a36Sopenharmony_ci */
2362306a36Sopenharmony_ci#define LPM_SETTING_OFF		0x0
2462306a36Sopenharmony_ci#define LPM_SETTING_ON		0x4
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_ci#define LPM_CUR_OFFSET		0x1c
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_ci#define AUTHEN_OFFSET		0x30
2962306a36Sopenharmony_ci#define CPULPM_EN		BIT(2)
3062306a36Sopenharmony_ci#define TZ_NS_SHIFT		9
3162306a36Sopenharmony_ci#define TZ_NS_MASK		BIT(9)
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_ci#define WHITE_LIST_SHIFT	16
3462306a36Sopenharmony_ci
3562306a36Sopenharmony_cistruct imx93_clk_gate {
3662306a36Sopenharmony_ci	struct clk_hw hw;
3762306a36Sopenharmony_ci	void __iomem	*reg;
3862306a36Sopenharmony_ci	u32		bit_idx;
3962306a36Sopenharmony_ci	u32		val;
4062306a36Sopenharmony_ci	u32		mask;
4162306a36Sopenharmony_ci	spinlock_t	*lock;
4262306a36Sopenharmony_ci	unsigned int	*share_count;
4362306a36Sopenharmony_ci};
4462306a36Sopenharmony_ci
4562306a36Sopenharmony_ci#define to_imx93_clk_gate(_hw) container_of(_hw, struct imx93_clk_gate, hw)
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_cistatic void imx93_clk_gate_do_hardware(struct clk_hw *hw, bool enable)
4862306a36Sopenharmony_ci{
4962306a36Sopenharmony_ci	struct imx93_clk_gate *gate = to_imx93_clk_gate(hw);
5062306a36Sopenharmony_ci	u32 val;
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_ci	val = readl(gate->reg + AUTHEN_OFFSET);
5362306a36Sopenharmony_ci	if (val & CPULPM_EN) {
5462306a36Sopenharmony_ci		val = enable ? LPM_SETTING_ON : LPM_SETTING_OFF;
5562306a36Sopenharmony_ci		writel(val, gate->reg + LPM_CUR_OFFSET);
5662306a36Sopenharmony_ci	} else {
5762306a36Sopenharmony_ci		val = readl(gate->reg + DIRECT_OFFSET);
5862306a36Sopenharmony_ci		val &= ~(gate->mask << gate->bit_idx);
5962306a36Sopenharmony_ci		if (enable)
6062306a36Sopenharmony_ci			val |= (gate->val & gate->mask) << gate->bit_idx;
6162306a36Sopenharmony_ci		writel(val, gate->reg + DIRECT_OFFSET);
6262306a36Sopenharmony_ci	}
6362306a36Sopenharmony_ci}
6462306a36Sopenharmony_ci
6562306a36Sopenharmony_cistatic int imx93_clk_gate_enable(struct clk_hw *hw)
6662306a36Sopenharmony_ci{
6762306a36Sopenharmony_ci	struct imx93_clk_gate *gate = to_imx93_clk_gate(hw);
6862306a36Sopenharmony_ci	unsigned long flags;
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_ci	spin_lock_irqsave(gate->lock, flags);
7162306a36Sopenharmony_ci
7262306a36Sopenharmony_ci	if (gate->share_count && (*gate->share_count)++ > 0)
7362306a36Sopenharmony_ci		goto out;
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_ci	imx93_clk_gate_do_hardware(hw, true);
7662306a36Sopenharmony_ciout:
7762306a36Sopenharmony_ci	spin_unlock_irqrestore(gate->lock, flags);
7862306a36Sopenharmony_ci
7962306a36Sopenharmony_ci	return 0;
8062306a36Sopenharmony_ci}
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_cistatic void imx93_clk_gate_disable(struct clk_hw *hw)
8362306a36Sopenharmony_ci{
8462306a36Sopenharmony_ci	struct imx93_clk_gate *gate = to_imx93_clk_gate(hw);
8562306a36Sopenharmony_ci	unsigned long flags;
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_ci	spin_lock_irqsave(gate->lock, flags);
8862306a36Sopenharmony_ci
8962306a36Sopenharmony_ci	if (gate->share_count) {
9062306a36Sopenharmony_ci		if (WARN_ON(*gate->share_count == 0))
9162306a36Sopenharmony_ci			goto out;
9262306a36Sopenharmony_ci		else if (--(*gate->share_count) > 0)
9362306a36Sopenharmony_ci			goto out;
9462306a36Sopenharmony_ci	}
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_ci	imx93_clk_gate_do_hardware(hw, false);
9762306a36Sopenharmony_ciout:
9862306a36Sopenharmony_ci	spin_unlock_irqrestore(gate->lock, flags);
9962306a36Sopenharmony_ci}
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_cistatic int imx93_clk_gate_reg_is_enabled(struct imx93_clk_gate *gate)
10262306a36Sopenharmony_ci{
10362306a36Sopenharmony_ci	u32 val = readl(gate->reg + AUTHEN_OFFSET);
10462306a36Sopenharmony_ci
10562306a36Sopenharmony_ci	if (val & CPULPM_EN) {
10662306a36Sopenharmony_ci		val = readl(gate->reg + LPM_CUR_OFFSET);
10762306a36Sopenharmony_ci		if (val == LPM_SETTING_ON)
10862306a36Sopenharmony_ci			return 1;
10962306a36Sopenharmony_ci	} else {
11062306a36Sopenharmony_ci		val = readl(gate->reg);
11162306a36Sopenharmony_ci		if (((val >> gate->bit_idx) & gate->mask) == gate->val)
11262306a36Sopenharmony_ci			return 1;
11362306a36Sopenharmony_ci	}
11462306a36Sopenharmony_ci
11562306a36Sopenharmony_ci	return 0;
11662306a36Sopenharmony_ci}
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_cistatic int imx93_clk_gate_is_enabled(struct clk_hw *hw)
11962306a36Sopenharmony_ci{
12062306a36Sopenharmony_ci	struct imx93_clk_gate *gate = to_imx93_clk_gate(hw);
12162306a36Sopenharmony_ci	unsigned long flags;
12262306a36Sopenharmony_ci	int ret;
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_ci	spin_lock_irqsave(gate->lock, flags);
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_ci	ret = imx93_clk_gate_reg_is_enabled(gate);
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_ci	spin_unlock_irqrestore(gate->lock, flags);
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_ci	return ret;
13162306a36Sopenharmony_ci}
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_cistatic void imx93_clk_gate_disable_unused(struct clk_hw *hw)
13462306a36Sopenharmony_ci{
13562306a36Sopenharmony_ci	struct imx93_clk_gate *gate = to_imx93_clk_gate(hw);
13662306a36Sopenharmony_ci	unsigned long flags;
13762306a36Sopenharmony_ci
13862306a36Sopenharmony_ci	spin_lock_irqsave(gate->lock, flags);
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_ci	if (!gate->share_count || *gate->share_count == 0)
14162306a36Sopenharmony_ci		imx93_clk_gate_do_hardware(hw, false);
14262306a36Sopenharmony_ci
14362306a36Sopenharmony_ci	spin_unlock_irqrestore(gate->lock, flags);
14462306a36Sopenharmony_ci}
14562306a36Sopenharmony_ci
14662306a36Sopenharmony_cistatic const struct clk_ops imx93_clk_gate_ops = {
14762306a36Sopenharmony_ci	.enable = imx93_clk_gate_enable,
14862306a36Sopenharmony_ci	.disable = imx93_clk_gate_disable,
14962306a36Sopenharmony_ci	.disable_unused = imx93_clk_gate_disable_unused,
15062306a36Sopenharmony_ci	.is_enabled = imx93_clk_gate_is_enabled,
15162306a36Sopenharmony_ci};
15262306a36Sopenharmony_ci
15362306a36Sopenharmony_cistatic const struct clk_ops imx93_clk_gate_ro_ops = {
15462306a36Sopenharmony_ci	.is_enabled = imx93_clk_gate_is_enabled,
15562306a36Sopenharmony_ci};
15662306a36Sopenharmony_ci
15762306a36Sopenharmony_cistruct clk_hw *imx93_clk_gate(struct device *dev, const char *name, const char *parent_name,
15862306a36Sopenharmony_ci			      unsigned long flags, void __iomem *reg, u32 bit_idx, u32 val,
15962306a36Sopenharmony_ci			      u32 mask, u32 domain_id, unsigned int *share_count)
16062306a36Sopenharmony_ci{
16162306a36Sopenharmony_ci	struct imx93_clk_gate *gate;
16262306a36Sopenharmony_ci	struct clk_hw *hw;
16362306a36Sopenharmony_ci	struct clk_init_data init;
16462306a36Sopenharmony_ci	int ret;
16562306a36Sopenharmony_ci	u32 authen;
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_ci	gate = kzalloc(sizeof(struct imx93_clk_gate), GFP_KERNEL);
16862306a36Sopenharmony_ci	if (!gate)
16962306a36Sopenharmony_ci		return ERR_PTR(-ENOMEM);
17062306a36Sopenharmony_ci
17162306a36Sopenharmony_ci	gate->reg = reg;
17262306a36Sopenharmony_ci	gate->lock = &imx_ccm_lock;
17362306a36Sopenharmony_ci	gate->bit_idx = bit_idx;
17462306a36Sopenharmony_ci	gate->val = val;
17562306a36Sopenharmony_ci	gate->mask = mask;
17662306a36Sopenharmony_ci	gate->share_count = share_count;
17762306a36Sopenharmony_ci
17862306a36Sopenharmony_ci	init.name = name;
17962306a36Sopenharmony_ci	init.ops = &imx93_clk_gate_ops;
18062306a36Sopenharmony_ci	init.flags = flags | CLK_SET_RATE_PARENT | CLK_OPS_PARENT_ENABLE;
18162306a36Sopenharmony_ci	init.parent_names = parent_name ? &parent_name : NULL;
18262306a36Sopenharmony_ci	init.num_parents = parent_name ? 1 : 0;
18362306a36Sopenharmony_ci
18462306a36Sopenharmony_ci	gate->hw.init = &init;
18562306a36Sopenharmony_ci	hw = &gate->hw;
18662306a36Sopenharmony_ci
18762306a36Sopenharmony_ci	authen = readl(reg + AUTHEN_OFFSET);
18862306a36Sopenharmony_ci	if (!(authen & TZ_NS_MASK) || !(authen & BIT(WHITE_LIST_SHIFT + domain_id)))
18962306a36Sopenharmony_ci		init.ops = &imx93_clk_gate_ro_ops;
19062306a36Sopenharmony_ci
19162306a36Sopenharmony_ci	ret = clk_hw_register(dev, hw);
19262306a36Sopenharmony_ci	if (ret) {
19362306a36Sopenharmony_ci		kfree(gate);
19462306a36Sopenharmony_ci		return ERR_PTR(ret);
19562306a36Sopenharmony_ci	}
19662306a36Sopenharmony_ci
19762306a36Sopenharmony_ci	return hw;
19862306a36Sopenharmony_ci}
19962306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(imx93_clk_gate);
200